@sriinnu/runic 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +8 -1
  3. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  4. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  5. package/RuniciOS/Views/ProviderListView.swift +10 -10
  6. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  7. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  8. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  9. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  10. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  11. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  12. package/Sources/Runic/About.swift +3 -3
  13. package/Sources/Runic/AppNotifications.swift +5 -3
  14. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
  15. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  16. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  17. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  18. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +18 -13
  19. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  20. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  21. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  22. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  23. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  24. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  25. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  26. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  28. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  29. package/Sources/Runic/Core/RunicColors.swift +6 -6
  30. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  31. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  32. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +1 -3
  33. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
  34. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
  35. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
  36. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
  37. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  38. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
  39. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
  40. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
  41. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
  42. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
  43. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
  44. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
  45. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
  46. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
  47. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
  48. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  49. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
  50. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
  51. package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
  52. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
  53. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  54. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  55. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  56. package/Sources/Runic/Core/Stores/UsageStore.swift +187 -161
  57. package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
  58. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
  59. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  60. package/Sources/Runic/ProviderRegistry.swift +1 -1
  61. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  62. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  63. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  81. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  83. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  84. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  85. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  87. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  88. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  107. package/Sources/Runic/RunicApp.swift +46 -36
  108. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  109. package/Sources/Runic/UsagePaceText.swift +1 -1
  110. package/Sources/Runic/UsageProgressBar.swift +13 -9
  111. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  112. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  113. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  114. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  115. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  116. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  117. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  118. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  119. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  120. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +12 -10
  121. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  122. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  123. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  124. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  125. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  126. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  127. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  128. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  129. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +18 -15
  130. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  131. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  132. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  133. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  134. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  135. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  136. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +22 -17
  137. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  138. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  139. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  140. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  141. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  142. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  143. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  144. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  145. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  146. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  147. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  148. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  149. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  150. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  151. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +44 -39
  152. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  153. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  154. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  155. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  156. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  157. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  158. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  159. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  160. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  161. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  162. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  163. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  164. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  165. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  166. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  167. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  168. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  169. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  170. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  171. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  172. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  173. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  174. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +73 -75
  175. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  176. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  177. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  178. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  179. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  180. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  181. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  182. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  183. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  184. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  185. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  186. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  187. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  188. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  189. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  190. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +3 -3
  191. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  192. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  193. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  194. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  195. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  196. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  197. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  198. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  199. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  200. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +1 -1
  201. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  202. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  204. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  205. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  206. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  207. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  208. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  209. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  210. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  211. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  212. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  213. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  214. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  215. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +2 -2
  216. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  217. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  218. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  219. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +5 -2
  220. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  221. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  222. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  225. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +7 -7
  226. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  227. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  228. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  229. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  230. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +23 -10
  231. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  232. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  233. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  234. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  235. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  236. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +26 -13
  237. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  240. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  241. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  242. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  243. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  244. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  245. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  246. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  247. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +48 -21
  248. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  249. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  250. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  251. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  252. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  253. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  254. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  255. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  256. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  257. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  258. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  259. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +27 -13
  260. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
  261. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
  262. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  263. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  264. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  265. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  266. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  267. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  268. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  269. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  270. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  271. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  272. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  273. package/Tests/RunicTests/CLICostTests.swift +6 -7
  274. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  275. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  276. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  277. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  278. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  279. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  280. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  281. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  282. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  283. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  284. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  285. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  286. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  287. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  288. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  289. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  290. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  291. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  292. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  293. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  294. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  295. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  296. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  297. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  298. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  299. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  300. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  301. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  302. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  303. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  304. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  305. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  306. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  307. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  308. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  309. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  310. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  311. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  312. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  313. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  314. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  315. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  316. package/Tests/RunicTests/RunicTests.swift +6 -7
  317. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  318. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  319. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  320. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  321. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  322. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  323. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  324. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  325. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  326. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  327. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  328. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  329. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  330. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  331. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  332. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  333. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  334. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  335. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  336. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  337. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  338. package/appcast.xml +36 -76
  339. package/package.json +1 -1
  340. package/runic.png +0 -0
  341. package/runic.svg +15 -0
  342. package/version.env +2 -2
@@ -1,48 +1,48 @@
1
1
  import AppKit
2
- import RunicCore
2
+ import CoreGraphics
3
3
  import Foundation
4
4
  import Observation
5
+ import RunicCore
5
6
  import Silo
6
- import CoreGraphics
7
7
 
8
- /// **UsageStore** - Main state management for AI provider usage tracking
9
- ///
10
- /// **Purpose:**
11
- /// Central store managing all provider usage data, snapshots, errors, and refresh logic.
12
- /// This is the single source of truth for the app's state.
13
- ///
14
- /// **Responsibilities:**
15
- /// - Fetch usage snapshots from all providers (Claude, Codex, Cursor, Gemini, etc.)
16
- /// - Manage authentication state and cookies via Silo
17
- /// - Track token usage across all providers
18
- /// - Coordinate "ping" operations (manual and automatic)
19
- /// - Handle errors and stale data detection
20
- /// - Provide Observable state for SwiftUI views
21
- ///
22
- /// **Performance Philosophy:**
23
- /// - **Zero Token Leakage** - Always use cookies/cached data first before API calls
24
- /// - **Single Ping Strategy** - One ping per provider per session, cache rest
25
- /// - **Stale Detection** - Mark data stale after 5 minutes (PerformanceConstants.staleDuration)
26
- ///
27
- /// **Known Issues:**
28
- /// - **God Class** - 2089 lines, needs refactoring (see PLAN.md)
29
- /// - Planned split: `UsageStateStore`, `UsageFetchingActor`, `TokenUsageService`
30
- ///
31
- /// **Dependencies:**
32
- /// - `RunicCore` - Provider descriptors and status probes
33
- /// - `Silo` - Browser cookie access
34
- /// - `Observation` - SwiftUI state updates
35
- ///
36
- /// **Usage:**
37
- /// ```swift
38
- /// @Observable
39
- /// final class UsageStore {
40
- /// var snapshots: [UsageProvider: ProviderSnapshot] = [:]
41
- /// var errors: [UsageProvider: Error] = [:]
42
- ///
43
- /// func ping(provider: UsageProvider) async { ... }
44
- /// }
45
- /// ```
8
+ // **UsageStore** - Main state management for AI provider usage tracking
9
+ //
10
+ // **Purpose:**
11
+ // Central store managing all provider usage data, snapshots, errors, and refresh logic.
12
+ // This is the single source of truth for the app's state.
13
+ //
14
+ // **Responsibilities:**
15
+ // - Fetch usage snapshots from all providers (Claude, Codex, Cursor, Gemini, etc.)
16
+ // - Manage authentication state and cookies via Silo
17
+ // - Track token usage across all providers
18
+ // - Coordinate "ping" operations (manual and automatic)
19
+ // - Handle errors and stale data detection
20
+ // - Provide Observable state for SwiftUI views
21
+ //
22
+ // **Performance Philosophy:**
23
+ // - **Zero Token Leakage** - Always use cookies/cached data first before API calls
24
+ // - **Single Ping Strategy** - One ping per provider per session, cache rest
25
+ // - **Stale Detection** - Mark data stale after 5 minutes (PerformanceConstants.staleDuration)
26
+ //
27
+ // **Known Issues:**
28
+ // - **God Class** - 2089 lines, needs refactoring (see PLAN.md)
29
+ // - Planned split: `UsageStateStore`, `UsageFetchingActor`, `TokenUsageService`
30
+ //
31
+ // **Dependencies:**
32
+ // - `RunicCore` - Provider descriptors and status probes
33
+ // - `Silo` - Browser cookie access
34
+ // - `Observation` - SwiftUI state updates
35
+ //
36
+ // **Usage:**
37
+ // ```swift
38
+ // @Observable
39
+ // final class UsageStore {
40
+ // var snapshots: [UsageProvider: ProviderSnapshot] = [:]
41
+ // var errors: [UsageProvider: Error] = [:]
42
+ //
43
+ // func ping(provider: UsageProvider) async { ... }
44
+ // }
45
+ // ```
46
46
 
47
47
  // MARK: - Observation helpers
48
48
 
@@ -305,8 +305,8 @@ struct ConsecutiveFailureGate {
305
305
  }
306
306
  }
307
307
 
308
- struct UsageLedgerAnomalySummary: Sendable, Hashable {
309
- enum Severity: Int, Sendable, Hashable {
308
+ struct UsageLedgerAnomalySummary: Hashable {
309
+ enum Severity: Int, Hashable {
310
310
  case elevated = 1
311
311
  case high = 2
312
312
  case critical = 3
@@ -320,8 +320,8 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
320
320
  }
321
321
  }
322
322
 
323
- struct MetricAnomaly: Sendable, Hashable {
324
- enum Metric: String, Sendable, Hashable {
323
+ struct MetricAnomaly: Hashable {
324
+ enum Metric: String, Hashable {
325
325
  case tokens
326
326
  case spend
327
327
 
@@ -340,8 +340,8 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
340
340
  let percentIncrease: Double
341
341
  }
342
342
 
343
- struct Explanation: Sendable, Hashable {
344
- struct Factor: Sendable, Hashable {
343
+ struct Explanation: Hashable {
344
+ struct Factor: Hashable {
345
345
  let metric: MetricAnomaly.Metric
346
346
  let severity: Severity
347
347
  let percentIncrease: Double
@@ -353,6 +353,7 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
353
353
  let primaryFactor: Factor
354
354
  let contributingFactors: [Factor]
355
355
  }
356
+
356
357
  let provider: UsageProvider
357
358
  let baselineDays: Int
358
359
  let tokenAnomaly: MetricAnomaly?
@@ -360,7 +361,7 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
360
361
 
361
362
  var primaryAnomaly: MetricAnomaly? {
362
363
  [self.tokenAnomaly, self.spendAnomaly]
363
- .compactMap { $0 }
364
+ .compactMap(\.self)
364
365
  .max { lhs, rhs in
365
366
  if lhs.severity != rhs.severity {
366
367
  return lhs.severity.rawValue < rhs.severity.rawValue
@@ -370,11 +371,11 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
370
371
  }
371
372
 
372
373
  func secondaryAnomaly(excluding metric: MetricAnomaly.Metric) -> MetricAnomaly? {
373
- let candidates = [self.tokenAnomaly, self.spendAnomaly].compactMap { $0 }
374
+ let candidates = [self.tokenAnomaly, self.spendAnomaly].compactMap(\.self)
374
375
  guard !candidates.isEmpty else { return nil }
375
376
  return candidates.first { $0.metric != metric }
376
377
  }
377
-
378
+
378
379
  var explanation: Explanation? {
379
380
  guard let primary = self.primaryAnomaly else { return nil }
380
381
  let primaryFactor = self.factor(from: primary)
@@ -522,7 +523,7 @@ enum UsageLedgerAnomalyDetector {
522
523
  }
523
524
  }
524
525
 
525
- struct ProviderHistoryDaySnapshot: Sendable, Hashable, Identifiable {
526
+ struct ProviderHistoryDaySnapshot: Hashable, Identifiable {
526
527
  let dayStart: Date
527
528
  let totals: UsageLedgerTotals
528
529
  let requestCount: Int
@@ -532,10 +533,12 @@ struct ProviderHistoryDaySnapshot: Sendable, Hashable, Identifiable {
532
533
  let modelSummaries: [UsageLedgerModelSummary]
533
534
  let projectSummaries: [UsageLedgerProjectSummary]
534
535
 
535
- var id: Date { self.dayStart }
536
+ var id: Date {
537
+ self.dayStart
538
+ }
536
539
  }
537
540
 
538
- struct ProviderHistoryMonthSnapshot: Sendable, Hashable {
541
+ struct ProviderHistoryMonthSnapshot: Hashable {
539
542
  let provider: UsageProvider
540
543
  let monthStart: Date
541
544
  let generatedAt: Date
@@ -544,6 +547,7 @@ struct ProviderHistoryMonthSnapshot: Sendable, Hashable {
544
547
  let note: String?
545
548
  let error: String?
546
549
  }
550
+
547
551
  @MainActor
548
552
  @Observable
549
553
  final class UsageStore {
@@ -629,16 +633,22 @@ final class UsageStore {
629
633
  @ObservationIgnored private var tokenTimerTask: Task<Void, Never>?
630
634
  @ObservationIgnored private var tokenRefreshSequenceTask: Task<Void, Never>?
631
635
  @ObservationIgnored private var ledgerRefreshTask: Task<Void, Never>?
632
- @ObservationIgnored private var providerHistoryMonthCache: [UsageProvider: [String: ProviderHistoryMonthCacheEntry]] = [:]
636
+ @ObservationIgnored private var providerHistoryMonthCache: [
637
+ UsageProvider: [String: ProviderHistoryMonthCacheEntry]
638
+ ] =
639
+ [:]
633
640
  @ObservationIgnored private var lastKnownSessionRemaining: [UsageProvider: Double] = [:]
634
641
  @ObservationIgnored private(set) var lastTokenFetchAt: [UsageProvider: Date] = [:]
635
642
  @ObservationIgnored private let tokenFetchTTL: TimeInterval = 60 * 60
636
643
  @ObservationIgnored private let tokenFetchTimeout: TimeInterval = 10 * 60
637
644
  @ObservationIgnored private let ledgerRefreshTTL: TimeInterval = 90
638
- @ObservationIgnored private var ledgerMaxAgeDays: Int { self.settings.ledgerMaxAgeDays }
645
+ @ObservationIgnored private var ledgerMaxAgeDays: Int {
646
+ self.settings.ledgerMaxAgeDays
647
+ }
648
+
639
649
  @ObservationIgnored private let providerHistoryCacheTTL: TimeInterval = 90
640
650
  @ObservationIgnored private let providerHistoryMaxScanDays: Int = 180
641
- @ObservationIgnored nonisolated(unsafe) private let performanceStorage: PerformanceStorageImpl?
651
+ @ObservationIgnored private nonisolated(unsafe) let performanceStorage: PerformanceStorageImpl?
642
652
 
643
653
  init(
644
654
  fetcher: UsageFetcher,
@@ -928,7 +938,7 @@ final class UsageStore {
928
938
  func ledgerProjectBreakdown(for provider: UsageProvider) -> [UsageLedgerProjectSummary] {
929
939
  self.ledgerProjectBreakdowns[provider] ?? []
930
940
  }
931
-
941
+
932
942
  func ledgerSpendForecast(for provider: UsageProvider) -> UsageLedgerSpendForecast? {
933
943
  self.ledgerSpendForecasts[provider]
934
944
  }
@@ -945,7 +955,6 @@ final class UsageStore {
945
955
  self.ledgerAnomalies[provider]
946
956
  }
947
957
 
948
-
949
958
  func ledgerError(for provider: UsageProvider) -> String? {
950
959
  self.ledgerErrors[provider]
951
960
  }
@@ -1084,27 +1093,27 @@ final class UsageStore {
1084
1093
  case .codex:
1085
1094
  return CodexUsageLogSource(maxAgeDays: maxAgeDays, now: now)
1086
1095
  case .copilot,
1087
- .gemini,
1088
- .antigravity,
1089
- .cursor,
1090
- .factory,
1091
- .zai,
1092
- .minimax,
1093
- .openrouter,
1094
- .groq,
1095
- .deepseek,
1096
- .fireworks,
1097
- .mistral,
1098
- .perplexity,
1099
- .kimi,
1100
- .auggie,
1101
- .together,
1102
- .cohere,
1103
- .xai,
1104
- .cerebras,
1105
- .sambanova,
1106
- .azure,
1107
- .bedrock:
1096
+ .gemini,
1097
+ .antigravity,
1098
+ .cursor,
1099
+ .factory,
1100
+ .zai,
1101
+ .minimax,
1102
+ .openrouter,
1103
+ .groq,
1104
+ .deepseek,
1105
+ .fireworks,
1106
+ .mistral,
1107
+ .perplexity,
1108
+ .kimi,
1109
+ .auggie,
1110
+ .together,
1111
+ .cohere,
1112
+ .xai,
1113
+ .cerebras,
1114
+ .sambanova,
1115
+ .azure,
1116
+ .bedrock:
1108
1117
  return self.providerOTelHistorySource(provider: provider, now: now, maxAgeDays: maxAgeDays)
1109
1118
  @unknown default:
1110
1119
  return nil
@@ -1131,7 +1140,7 @@ final class UsageStore {
1131
1140
  minTimestamp: cutoff)
1132
1141
  }
1133
1142
 
1134
- private struct UsageLedgerProviderFilterSource: UsageLedgerSource, Sendable {
1143
+ private struct UsageLedgerProviderFilterSource: UsageLedgerSource {
1135
1144
  private let source: any UsageLedgerSource
1136
1145
  private let provider: UsageProvider
1137
1146
  private let minTimestamp: Date?
@@ -1165,7 +1174,7 @@ final class UsageStore {
1165
1174
  Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_GENAI_LOG_PATH"]),
1166
1175
  Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_LOG_PATHS"]),
1167
1176
  Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_LOG_PATH"]),
1168
- ].flatMap { $0 }
1177
+ ].flatMap(\.self)
1169
1178
 
1170
1179
  let urls = candidatePaths.compactMap(Self.expandTildePath)
1171
1180
  return Self.discoverOTelLedgerFiles(from: urls)
@@ -1250,7 +1259,7 @@ final class UsageStore {
1250
1259
  error: nil)
1251
1260
  }
1252
1261
 
1253
- nonisolated private static func providerHistoryDays(
1262
+ private nonisolated static func providerHistoryDays(
1254
1263
  entries: [UsageLedgerEntry],
1255
1264
  timeZone: TimeZone) -> [ProviderHistoryDaySnapshot]
1256
1265
  {
@@ -1377,12 +1386,11 @@ final class UsageStore {
1377
1386
 
1378
1387
  // Track successful latency
1379
1388
  await self.trackLatency(
1380
- provider: .codex, // TODO: Add custom provider tracking
1389
+ provider: .codex, // TODO: Add custom provider tracking
1381
1390
  requestID: requestID,
1382
1391
  startTime: startTime,
1383
1392
  endTime: endTime,
1384
- success: true
1385
- )
1393
+ success: true)
1386
1394
 
1387
1395
  let snapshot = CustomProviderSnapshot.from(usageData: usageData.toCustomUsageData(), config: config)
1388
1396
  await MainActor.run {
@@ -1394,13 +1402,12 @@ final class UsageStore {
1394
1402
 
1395
1403
  // Track failed latency and error
1396
1404
  await self.trackLatency(
1397
- provider: .codex, // TODO: Add custom provider tracking
1405
+ provider: .codex, // TODO: Add custom provider tracking
1398
1406
  requestID: requestID,
1399
1407
  startTime: startTime,
1400
1408
  endTime: endTime,
1401
- success: false
1402
- )
1403
- await self.trackError(provider: .codex, error: error) // TODO: Add custom provider tracking
1409
+ success: false)
1410
+ await self.trackError(provider: .codex, error: error) // TODO: Add custom provider tracking
1404
1411
 
1405
1412
  await MainActor.run {
1406
1413
  self.customProviderErrors[id] = error.localizedDescription
@@ -1534,11 +1541,11 @@ final class UsageStore {
1534
1541
  private func systemPauseDetailText(_ reason: AutoRefreshSuspensionReason) -> String {
1535
1542
  switch reason {
1536
1543
  case .systemSleep:
1537
- return "your Mac went to sleep"
1544
+ "your Mac went to sleep"
1538
1545
  case .screenSleep:
1539
- return "the display went to sleep"
1546
+ "the display went to sleep"
1540
1547
  case .sessionInactive:
1541
- return "your session became inactive"
1548
+ "your session became inactive"
1542
1549
  }
1543
1550
  }
1544
1551
 
@@ -1628,11 +1635,11 @@ final class UsageStore {
1628
1635
  func windowChanged(_ lhs: RateWindow?, _ rhs: RateWindow?) -> Bool {
1629
1636
  switch (lhs, rhs) {
1630
1637
  case (nil, nil):
1631
- return false
1638
+ false
1632
1639
  case let (l?, r?):
1633
- return abs(l.usedPercent - r.usedPercent) >= 0.1
1640
+ abs(l.usedPercent - r.usedPercent) >= 0.1
1634
1641
  case (nil, _), (_, nil):
1635
- return true
1642
+ true
1636
1643
  }
1637
1644
  }
1638
1645
 
@@ -1670,7 +1677,8 @@ final class UsageStore {
1670
1677
  if self.ledgerRefreshTask != nil { return }
1671
1678
 
1672
1679
  // Immediately populate from cache so the UI has data before the background scan.
1673
- let providersToCache = providers.filter { self.ledgerAllDailySummaries[$0] == nil || self.ledgerAllDailySummaries[$0]?.isEmpty == true }
1680
+ let providersToCache = providers
1681
+ .filter { self.ledgerAllDailySummaries[$0] == nil || self.ledgerAllDailySummaries[$0]?.isEmpty == true }
1674
1682
  if !providersToCache.isEmpty {
1675
1683
  Task {
1676
1684
  let cache = LedgerCache.shared
@@ -1681,7 +1689,9 @@ final class UsageStore {
1681
1689
  if !summaries.isEmpty {
1682
1690
  await MainActor.run { [weak self] in
1683
1691
  guard let self else { return }
1684
- if self.ledgerAllDailySummaries[provider] == nil || self.ledgerAllDailySummaries[provider]?.isEmpty == true {
1692
+ if self.ledgerAllDailySummaries[provider] == nil || self.ledgerAllDailySummaries[provider]?
1693
+ .isEmpty == true
1694
+ {
1685
1695
  self.ledgerAllDailySummaries[provider] = summaries
1686
1696
  // Also populate today's summary from cache.
1687
1697
  let todayStart = Calendar.current.startOfDay(for: now)
@@ -1842,7 +1852,7 @@ final class UsageStore {
1842
1852
  self.ledgerRefreshTask?.cancel()
1843
1853
  }
1844
1854
 
1845
- private struct LedgerRefreshResult: Sendable {
1855
+ private struct LedgerRefreshResult {
1846
1856
  let dailyByProvider: [UsageProvider: UsageLedgerDailySummary]
1847
1857
  let allDailySummariesByProvider: [UsageProvider: [UsageLedgerDailySummary]]
1848
1858
  let hourlySummariesByProvider: [UsageProvider: [UsageLedgerHourlySummary]]
@@ -1870,7 +1880,7 @@ final class UsageStore {
1870
1880
  now: Date,
1871
1881
  inactiveProviders: Set<UsageProvider>) -> [(UsageProvider, any UsageLedgerSource)]
1872
1882
  {
1873
- return UsageProvider.allCases.compactMap { provider in
1883
+ UsageProvider.allCases.compactMap { provider in
1874
1884
  guard self.isEnabled(provider), !inactiveProviders.contains(provider) else { return nil }
1875
1885
  guard let source = self.providerHistorySource(
1876
1886
  provider: provider,
@@ -1886,23 +1896,23 @@ final class UsageStore {
1886
1896
  now: Date) async -> LedgerRefreshResult
1887
1897
  {
1888
1898
  guard !sources.isEmpty else {
1889
- return LedgerRefreshResult(
1890
- dailyByProvider: [:],
1891
- allDailySummariesByProvider: [:],
1892
- hourlySummariesByProvider: [:],
1893
- activeBlocksByProvider: [:],
1894
- topModelsByProvider: [:],
1895
- topProjectsByProvider: [:],
1896
- modelBreakdownsByProvider: [:],
1897
- projectBreakdownsByProvider: [:],
1898
- spendForecastsByProvider: [:],
1899
- projectSpendForecastsByProvider: [:],
1900
- topProjectSpendForecastsByProvider: [:],
1901
- anomaliesByProvider: [:],
1902
- errorsByProvider: [:],
1903
- lastActivityByProvider: [:],
1904
- updatedAt: now,
1905
- providers: [])
1899
+ return LedgerRefreshResult(
1900
+ dailyByProvider: [:],
1901
+ allDailySummariesByProvider: [:],
1902
+ hourlySummariesByProvider: [:],
1903
+ activeBlocksByProvider: [:],
1904
+ topModelsByProvider: [:],
1905
+ topProjectsByProvider: [:],
1906
+ modelBreakdownsByProvider: [:],
1907
+ projectBreakdownsByProvider: [:],
1908
+ spendForecastsByProvider: [:],
1909
+ projectSpendForecastsByProvider: [:],
1910
+ topProjectSpendForecastsByProvider: [:],
1911
+ anomaliesByProvider: [:],
1912
+ errorsByProvider: [:],
1913
+ lastActivityByProvider: [:],
1914
+ updatedAt: now,
1915
+ providers: [])
1906
1916
  }
1907
1917
 
1908
1918
  let providers = sources.map(\.0)
@@ -2006,10 +2016,10 @@ final class UsageStore {
2006
2016
  projectBreakdownsByProvider[summary.provider, default: []].append(summary)
2007
2017
  }
2008
2018
 
2009
- let providerForecasts = UsageLedgerAggregator.providerSpendForecasts(
2010
- entries: entries,
2011
- now: now,
2012
- timeZone: timeZone)
2019
+ let providerForecasts = UsageLedgerAggregator.providerSpendForecasts(
2020
+ entries: entries,
2021
+ now: now,
2022
+ timeZone: timeZone)
2013
2023
  var spendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
2014
2024
  for forecast in providerForecasts where spendForecastsByProvider[forecast.provider] == nil {
2015
2025
  spendForecastsByProvider[forecast.provider] = forecast
@@ -2032,34 +2042,34 @@ final class UsageStore {
2032
2042
  }
2033
2043
 
2034
2044
  var topProjectSpendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
2035
- for (provider, summary) in topProjectsByProvider {
2036
- guard let forecasts = projectSpendForecastsByProvider[provider] else { continue }
2037
- if let matched = self.matchingProjectForecast(for: summary, forecasts: forecasts) {
2038
- topProjectSpendForecastsByProvider[provider] = matched
2045
+ for (provider, summary) in topProjectsByProvider {
2046
+ guard let forecasts = projectSpendForecastsByProvider[provider] else { continue }
2047
+ if let matched = self.matchingProjectForecast(for: summary, forecasts: forecasts) {
2048
+ topProjectSpendForecastsByProvider[provider] = matched
2049
+ }
2039
2050
  }
2040
- }
2041
- let anomaliesByProvider = UsageLedgerAnomalyDetector.summaries(
2042
- dailySummaries: dailySummaries,
2043
- now: now,
2044
- calendar: calendar)
2051
+ let anomaliesByProvider = UsageLedgerAnomalyDetector.summaries(
2052
+ dailySummaries: dailySummaries,
2053
+ now: now,
2054
+ calendar: calendar)
2045
2055
 
2046
2056
  return LedgerRefreshResult(
2047
2057
  dailyByProvider: dailyByProvider,
2048
2058
  allDailySummariesByProvider: allDailySummariesByProvider,
2049
2059
  hourlySummariesByProvider: hourlySummariesByProvider,
2050
- activeBlocksByProvider: activeByProvider,
2051
- topModelsByProvider: topModelsByProvider,
2052
- topProjectsByProvider: topProjectsByProvider,
2053
- modelBreakdownsByProvider: modelBreakdownsByProvider,
2054
- projectBreakdownsByProvider: projectBreakdownsByProvider,
2055
- spendForecastsByProvider: spendForecastsByProvider,
2056
- projectSpendForecastsByProvider: projectSpendForecastsByProvider,
2057
- topProjectSpendForecastsByProvider: topProjectSpendForecastsByProvider,
2058
- anomaliesByProvider: anomaliesByProvider,
2059
- errorsByProvider: errors,
2060
- lastActivityByProvider: lastActivityByProvider,
2061
- updatedAt: now,
2062
- providers: providers)
2060
+ activeBlocksByProvider: activeByProvider,
2061
+ topModelsByProvider: topModelsByProvider,
2062
+ topProjectsByProvider: topProjectsByProvider,
2063
+ modelBreakdownsByProvider: modelBreakdownsByProvider,
2064
+ projectBreakdownsByProvider: projectBreakdownsByProvider,
2065
+ spendForecastsByProvider: spendForecastsByProvider,
2066
+ projectSpendForecastsByProvider: projectSpendForecastsByProvider,
2067
+ topProjectSpendForecastsByProvider: topProjectSpendForecastsByProvider,
2068
+ anomaliesByProvider: anomaliesByProvider,
2069
+ errorsByProvider: errors,
2070
+ lastActivityByProvider: lastActivityByProvider,
2071
+ updatedAt: now,
2072
+ providers: providers)
2063
2073
  }
2064
2074
 
2065
2075
  private func projectNameOverridesFromBudgets() -> [String: String] {
@@ -2806,9 +2816,11 @@ extension UsageStore {
2806
2816
  ? nil : self.settings.azureOpenAIAPIToken,
2807
2817
  endpoint: self.settings.azureOpenAIEndpoint.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
2808
2818
  ? nil : self.settings.azureOpenAIEndpoint,
2809
- deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
2819
+ deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines)
2820
+ .isEmpty
2810
2821
  ? nil : self.settings.azureOpenAIDeployment,
2811
- apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
2822
+ apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines)
2823
+ .isEmpty
2812
2824
  ? nil : self.settings.azureOpenAIAPIVersion),
2813
2825
  bedrock: ProviderSettingsSnapshot.BedrockProviderSettings(
2814
2826
  region: self.settings.bedrockRegion.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
@@ -3140,8 +3152,8 @@ extension UsageStore {
3140
3152
  context: ProviderFetchContext,
3141
3153
  settings: ProviderSettingsSnapshot) async -> String
3142
3154
  {
3143
- var lines: [String] = [
3144
- "provider=\(provider.rawValue)"
3155
+ var lines = [
3156
+ "provider=\(provider.rawValue)",
3145
3157
  ]
3146
3158
 
3147
3159
  lines.append(contentsOf: self.debugCredentialLines(for: provider, settings: settings))
@@ -3186,38 +3198,54 @@ extension UsageStore {
3186
3198
  resolution: ProviderTokenResolver.minimaxGroupResolution()),
3187
3199
  ]
3188
3200
  case .openrouter:
3189
- return [self.debugTokenSummary(provider: "openrouter", resolution: ProviderTokenResolver.openRouterResolution())]
3201
+ return [self.debugTokenSummary(
3202
+ provider: "openrouter",
3203
+ resolution: ProviderTokenResolver.openRouterResolution())]
3190
3204
  case .groq:
3191
3205
  return [self.debugTokenSummary(provider: "groq", resolution: ProviderTokenResolver.groqResolution())]
3192
3206
  case .deepseek:
3193
- return [self.debugTokenSummary(provider: "deepseek", resolution: ProviderTokenResolver.deepSeekResolution())]
3207
+ return [self.debugTokenSummary(
3208
+ provider: "deepseek",
3209
+ resolution: ProviderTokenResolver.deepSeekResolution())]
3194
3210
  case .fireworks:
3195
- return [self.debugTokenSummary(provider: "fireworks", resolution: ProviderTokenResolver.fireworksResolution())]
3211
+ return [self.debugTokenSummary(
3212
+ provider: "fireworks",
3213
+ resolution: ProviderTokenResolver.fireworksResolution())]
3196
3214
  case .mistral:
3197
3215
  return [self.debugTokenSummary(provider: "mistral", resolution: ProviderTokenResolver.mistralResolution())]
3198
3216
  case .perplexity:
3199
- return [self.debugTokenSummary(provider: "perplexity", resolution: ProviderTokenResolver.perplexityResolution())]
3217
+ return [self.debugTokenSummary(
3218
+ provider: "perplexity",
3219
+ resolution: ProviderTokenResolver.perplexityResolution())]
3200
3220
  case .kimi:
3201
3221
  return [self.debugTokenSummary(provider: "kimi", resolution: ProviderTokenResolver.kimiResolution())]
3202
3222
  case .auggie:
3203
3223
  return [self.debugTokenSummary(provider: "auggie", resolution: ProviderTokenResolver.auggieResolution())]
3204
3224
  case .together:
3205
- return [self.debugTokenSummary(provider: "together", resolution: ProviderTokenResolver.togetherResolution())]
3225
+ return [self.debugTokenSummary(
3226
+ provider: "together",
3227
+ resolution: ProviderTokenResolver.togetherResolution())]
3206
3228
  case .cohere:
3207
3229
  return [self.debugTokenSummary(provider: "cohere", resolution: ProviderTokenResolver.cohereResolution())]
3208
3230
  case .xai:
3209
3231
  return [self.debugTokenSummary(provider: "xai", resolution: ProviderTokenResolver.xaiResolution())]
3210
3232
  case .cerebras:
3211
- return [self.debugTokenSummary(provider: "cerebras", resolution: ProviderTokenResolver.cerebrasResolution())]
3233
+ return [self.debugTokenSummary(
3234
+ provider: "cerebras",
3235
+ resolution: ProviderTokenResolver.cerebrasResolution())]
3212
3236
  case .sambanova:
3213
- return [self.debugTokenSummary(provider: "sambanova", resolution: ProviderTokenResolver.sambaNovaResolution())]
3237
+ return [self.debugTokenSummary(
3238
+ provider: "sambanova",
3239
+ resolution: ProviderTokenResolver.sambaNovaResolution())]
3214
3240
  case .azure:
3215
3241
  var lines: [String] = [
3216
3242
  "azure.endpoint=\(self.debugFieldValue(settings.azure?.endpoint))",
3217
3243
  "azure.deployment=\(self.debugFieldValue(settings.azure?.deployment))",
3218
- "azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))"
3244
+ "azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))",
3219
3245
  ]
3220
- lines.append(self.debugTokenSummary(provider: "azure.token", resolution: ProviderTokenResolver.azureOpenAIResolution()))
3246
+ lines.append(self.debugTokenSummary(
3247
+ provider: "azure.token",
3248
+ resolution: ProviderTokenResolver.azureOpenAIResolution()))
3221
3249
  return lines
3222
3250
  case .bedrock:
3223
3251
  return [
@@ -3277,7 +3305,7 @@ extension UsageStore {
3277
3305
  let resetsAt = providerCost.resetsAt?.description ?? "nil"
3278
3306
  lines.append(
3279
3307
  "provider_cost.used=\(providerCost.used) limit=\(providerCost.limit) " +
3280
- "currency=\(providerCost.currencyCode) period=\(providerCost.period ?? "nil") resetsAt=\(resetsAt)")
3308
+ "currency=\(providerCost.currencyCode) period=\(providerCost.period ?? "nil") resetsAt=\(resetsAt)")
3281
3309
  }
3282
3310
  return lines
3283
3311
  }
@@ -3526,8 +3554,8 @@ extension UsageStore {
3526
3554
  requestID: String,
3527
3555
  startTime: Date,
3528
3556
  endTime: Date,
3529
- success: Bool
3530
- ) async {
3557
+ success: Bool) async
3558
+ {
3531
3559
  guard let storage = self.performanceStorage else { return }
3532
3560
 
3533
3561
  let metric = LatencyMetric(
@@ -3539,8 +3567,7 @@ extension UsageStore {
3539
3567
  endTime: endTime,
3540
3568
  durationMs: Int(endTime.timeIntervalSince(startTime) * 1000),
3541
3569
  success: success,
3542
- createdAt: Date()
3543
- )
3570
+ createdAt: Date())
3544
3571
 
3545
3572
  try? await storage.save(latency: metric)
3546
3573
  }
@@ -3556,8 +3583,7 @@ extension UsageStore {
3556
3583
  errorType: errorType,
3557
3584
  errorMessage: error.localizedDescription,
3558
3585
  retryCount: 0,
3559
- timestamp: Date()
3560
- )
3586
+ timestamp: Date())
3561
3587
 
3562
3588
  try? await storage.save(error: errorEvent)
3563
3589
  }
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Security
4
4
 
5
5
  protocol XAITokenStoring: Sendable {
@@ -64,7 +64,6 @@ struct KeychainXAITokenStore: XAITokenStoring {
64
64
  addQuery[kSecValueData as String] = data
65
65
  addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
66
66
 
67
-
68
67
  let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
69
68
  guard addStatus == errSecSuccess else {
70
69
  Self.log.error("Keychain add failed: \(addStatus)")
@@ -115,5 +114,4 @@ struct KeychainXAITokenStore: XAITokenStoring {
115
114
  }
116
115
  return q
117
116
  }
118
-
119
117
  }
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Security
4
4
 
5
5
  protocol ZaiTokenStoring: Sendable {
@@ -114,5 +114,4 @@ struct KeychainZaiTokenStore: ZaiTokenStoring {
114
114
  }
115
115
  return q
116
116
  }
117
-
118
117
  }