@sriinnu/runic 1.0.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 (348) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +1 -1
  3. package/README.md +47 -29
  4. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  5. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  6. package/RuniciOS/Views/ProviderListView.swift +10 -10
  7. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  8. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  9. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  10. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  11. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  12. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  13. package/Scripts/package_app.sh +2 -2
  14. package/Sources/Runic/About.swift +3 -3
  15. package/Sources/Runic/AppNotifications.swift +5 -3
  16. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
  17. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  18. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  19. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  20. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
  21. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  22. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  23. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  24. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  25. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  26. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  28. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
  29. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  30. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  31. package/Sources/Runic/Core/RunicColors.swift +6 -6
  32. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  33. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  34. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
  35. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
  36. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
  37. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
  38. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
  39. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  40. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
  41. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
  42. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
  43. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
  44. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
  45. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
  46. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
  47. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
  48. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
  49. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
  50. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  51. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
  52. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
  53. package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
  54. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
  55. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  56. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  57. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  58. package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
  59. package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
  60. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
  61. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  62. package/Sources/Runic/ProviderRegistry.swift +1 -1
  63. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  81. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  83. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  84. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  85. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  87. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  88. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  107. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  108. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  109. package/Sources/Runic/Resources/provider-context-windows.json +20 -0
  110. package/Sources/Runic/RunicApp.swift +46 -36
  111. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  112. package/Sources/Runic/UsagePaceText.swift +1 -1
  113. package/Sources/Runic/UsageProgressBar.swift +13 -9
  114. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  115. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  116. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  117. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  118. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  119. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  120. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  121. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  122. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  123. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
  124. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  125. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  126. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  127. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  128. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  129. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  130. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  131. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  132. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
  133. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  134. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  135. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  136. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  137. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  138. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  139. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
  140. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  141. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  142. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  143. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  144. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  145. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  146. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  147. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  148. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  149. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  150. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  151. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  152. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  153. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  154. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
  155. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  156. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  157. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  158. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  159. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  160. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  161. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  162. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  163. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  164. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  165. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  166. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  167. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  168. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  169. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  170. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  171. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  172. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  173. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  174. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  175. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  176. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  177. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
  178. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  179. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  180. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  181. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
  182. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  183. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  184. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  185. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  186. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  187. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  188. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  189. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  190. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  191. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  192. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  193. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  194. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
  195. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  196. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  197. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  198. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  199. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  200. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  201. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  202. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  204. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
  205. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
  206. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  207. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  208. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  209. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  210. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  211. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  212. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  213. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  214. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  215. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  216. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
  217. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  218. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  219. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  220. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  221. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
  222. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  225. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
  226. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  227. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  228. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  229. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  230. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  231. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
  232. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  233. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  234. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  235. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  236. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
  237. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  240. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  241. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  242. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
  243. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  244. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  245. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  246. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  247. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  248. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  249. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  250. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  251. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  252. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  253. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
  254. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  255. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  256. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  257. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  258. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  259. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  260. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  261. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  262. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  263. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  264. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  265. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
  266. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
  267. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
  268. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  269. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  270. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  271. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  272. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  273. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  274. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  275. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  276. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  277. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  278. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  279. package/Tests/RunicTests/CLICostTests.swift +6 -7
  280. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  281. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  282. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  283. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  284. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  285. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  286. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  287. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  288. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  289. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  290. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  291. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  292. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  293. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  294. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  295. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  296. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  297. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  298. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  299. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  300. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  301. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  302. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  303. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  304. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  305. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  306. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  307. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  308. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  309. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  310. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  311. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  312. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  313. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  314. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  315. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  316. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  317. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  318. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  319. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  320. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  321. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  322. package/Tests/RunicTests/RunicTests.swift +6 -7
  323. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  324. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  325. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  326. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  327. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  328. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  329. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  330. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  331. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  332. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  333. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  334. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  335. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  336. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  337. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  338. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  339. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  340. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  341. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  342. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  343. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  344. package/appcast.xml +36 -37
  345. package/package.json +1 -1
  346. package/runic.png +0 -0
  347. package/runic.svg +15 -0
  348. package/version.env +2 -2
@@ -4,19 +4,19 @@ import SwiftUI
4
4
 
5
5
  enum MenuContentMetrics {
6
6
  /// Horizontal padding for menu content
7
- static let horizontalPadding: CGFloat = RunicSpacing.sm // 12
7
+ static let horizontalPadding: CGFloat = RunicSpacing.sm // 12
8
8
 
9
9
  /// Vertical padding for menu content
10
- static let verticalPadding: CGFloat = RunicSpacing.xs // 8
10
+ static let verticalPadding: CGFloat = RunicSpacing.xs // 8
11
11
 
12
12
  /// Spacing between menu sections
13
- static let sectionSpacing: CGFloat = RunicSpacing.xs // 8
13
+ static let sectionSpacing: CGFloat = RunicSpacing.xs // 8
14
14
 
15
15
  /// Spacing between menu entries
16
- static let entrySpacing: CGFloat = RunicSpacing.xxs // 4
16
+ static let entrySpacing: CGFloat = RunicSpacing.xxs // 4
17
17
 
18
18
  /// Minimum menu width
19
- static let minWidth: CGFloat = 260
19
+ static let minWidth: CGFloat = 290
20
20
  }
21
21
 
22
22
  @MainActor
@@ -52,6 +52,7 @@ struct MenuContent: View {
52
52
  .padding(.horizontal, MenuContentMetrics.horizontalPadding)
53
53
  .padding(.vertical, MenuContentMetrics.verticalPadding)
54
54
  .frame(minWidth: MenuContentMetrics.minWidth, alignment: .leading)
55
+ .runicTypography()
55
56
  }
56
57
 
57
58
  @ViewBuilder
@@ -60,11 +61,11 @@ struct MenuContent: View {
60
61
  case let .text(text, style):
61
62
  switch style {
62
63
  case .headline:
63
- Text(text).font(.headline)
64
+ Text(text).font(RunicFont.headline)
64
65
  case .primary:
65
66
  Text(text)
66
67
  case .secondary:
67
- Text(text).foregroundStyle(.secondary).font(.footnote)
68
+ Text(text).foregroundStyle(.secondary).font(RunicFont.footnote)
68
69
  }
69
70
  case let .action(title, action):
70
71
  Button {
@@ -89,7 +90,9 @@ struct MenuContent: View {
89
90
  }
90
91
  }
91
92
 
92
- private func iconName(for action: MenuDescriptor.MenuAction) -> String? { action.systemImageName }
93
+ private func iconName(for action: MenuDescriptor.MenuAction) -> String? {
94
+ action.systemImageName
95
+ }
93
96
 
94
97
  private func perform(_ action: MenuDescriptor.MenuAction) {
95
98
  switch action {
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
 
4
4
  @MainActor
5
5
  struct MenuDescriptor {
@@ -351,7 +351,9 @@ private enum AccountFormatter {
351
351
  return cleaned.isEmpty ? text : cleaned
352
352
  }
353
353
 
354
- static func email(_ text: String) -> String { text }
354
+ static func email(_ text: String) -> String {
355
+ text
356
+ }
355
357
  }
356
358
 
357
359
  extension MenuDescriptor.MenuAction {
@@ -1,14 +1,7 @@
1
1
  import SwiftUI
2
2
 
3
- private struct MenuItemHighlightedKey: EnvironmentKey {
4
- static let defaultValue = false
5
- }
6
-
7
3
  extension EnvironmentValues {
8
- var menuItemHighlighted: Bool {
9
- get { self[MenuItemHighlightedKey.self] }
10
- set { self[MenuItemHighlightedKey.self] = newValue }
11
- }
4
+ @Entry var menuItemHighlighted: Bool = false
12
5
  }
13
6
 
14
7
  enum MenuHighlightStyle {
@@ -20,16 +20,18 @@ struct ModelBreakdownMenuView: View {
20
20
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
21
21
  if model.items.isEmpty {
22
22
  Text("No model data.")
23
- .font(.footnote)
23
+ .font(RunicFont.footnote)
24
24
  .foregroundStyle(.secondary)
25
25
  } else {
26
26
  // MARK: - Title
27
+
27
28
  Text("Models")
28
- .font(.caption)
29
+ .font(RunicFont.caption)
29
30
  .fontWeight(.semibold)
30
31
  .foregroundStyle(.primary)
31
32
 
32
33
  // MARK: - Donut chart
34
+
33
35
  Chart {
34
36
  ForEach(model.chartItems) { item in
35
37
  SectorMark(
@@ -45,6 +47,7 @@ struct ModelBreakdownMenuView: View {
45
47
  .accessibilityLabel(Self.chartAccessibilityLabel(model: model))
46
48
 
47
49
  // MARK: - Model list
50
+
48
51
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
49
52
  ForEach(model.chartItems) { item in
50
53
  Self.modelRow(item: item)
@@ -52,28 +55,31 @@ struct ModelBreakdownMenuView: View {
52
55
  }
53
56
 
54
57
  // MARK: - Overflow note
58
+
55
59
  if model.overflowCount > 0 {
56
60
  Text("and \(model.overflowCount) more")
57
- .font(.caption2)
61
+ .font(RunicFont.caption2)
58
62
  .foregroundStyle(.tertiary)
59
63
  }
60
64
 
61
65
  // MARK: - Cache hit rate
66
+
62
67
  if let cacheRate = model.cacheHitRateText {
63
68
  Divider()
64
69
  HStack(spacing: RunicSpacing.xxs) {
65
70
  Text("Cache hit rate")
66
- .font(.caption)
71
+ .font(RunicFont.caption)
67
72
  .foregroundStyle(.secondary)
68
73
  Spacer(minLength: RunicSpacing.xxs)
69
74
  Text(cacheRate)
70
- .font(.caption)
75
+ .font(RunicFont.caption)
71
76
  .fontWeight(.medium)
72
77
  .foregroundStyle(.primary)
73
78
  }
74
79
  }
75
80
 
76
81
  // MARK: - Total summary
82
+
77
83
  Divider()
78
84
  Self.totalRow(model: model)
79
85
  }
@@ -91,27 +97,27 @@ struct ModelBreakdownMenuView: View {
91
97
  .fill(item.color)
92
98
  .frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
93
99
  Text(item.displayName)
94
- .font(.caption)
100
+ .font(RunicFont.caption)
95
101
  .foregroundStyle(.primary)
96
102
  .lineLimit(1)
97
103
  .truncationMode(.tail)
98
104
  Spacer(minLength: RunicSpacing.xxs)
99
105
  VStack(alignment: .trailing, spacing: 0) {
100
106
  Text("\(UsageFormatter.tokenCountString(item.totalTokens)) tokens")
101
- .font(.system(.caption2, design: .rounded))
107
+ .font(RunicFont.caption2)
102
108
  .foregroundStyle(.secondary)
103
109
  if let context = UsageFormatter.modelContextLabel(for: item.model) {
104
110
  Text(context)
105
- .font(.system(.caption2, design: .rounded))
111
+ .font(RunicFont.caption2)
106
112
  .foregroundStyle(.tertiary)
107
113
  }
108
114
  HStack(spacing: RunicSpacing.xxxs) {
109
115
  Text("\(item.requestCount) req\(item.requestCount == 1 ? "" : "s")")
110
- .font(.system(.caption2, design: .rounded))
116
+ .font(RunicFont.caption2)
111
117
  .foregroundStyle(.tertiary)
112
118
  if let cost = item.costText {
113
119
  Text(cost)
114
- .font(.system(.caption2, design: .rounded))
120
+ .font(RunicFont.caption2)
115
121
  .foregroundStyle(.tertiary)
116
122
  }
117
123
  }
@@ -122,21 +128,21 @@ struct ModelBreakdownMenuView: View {
122
128
  private static func totalRow(model: ModelModel) -> some View {
123
129
  HStack {
124
130
  Text("Total")
125
- .font(.caption)
131
+ .font(RunicFont.caption)
126
132
  .fontWeight(.medium)
127
133
  .foregroundStyle(.primary)
128
134
  Spacer(minLength: RunicSpacing.xxs)
129
135
  VStack(alignment: .trailing, spacing: 0) {
130
136
  Text("\(UsageFormatter.tokenCountString(model.grandTotalTokens)) tokens")
131
- .font(.system(.caption2, design: .rounded))
137
+ .font(RunicFont.caption2)
132
138
  .foregroundStyle(.secondary)
133
139
  HStack(spacing: RunicSpacing.xxxs) {
134
140
  Text("\(model.grandTotalRequests) requests")
135
- .font(.system(.caption2, design: .rounded))
141
+ .font(RunicFont.caption2)
136
142
  .foregroundStyle(.tertiary)
137
143
  if let cost = model.grandTotalCostText {
138
144
  Text(cost)
139
- .font(.system(.caption2, design: .rounded))
145
+ .font(RunicFont.caption2)
140
146
  .foregroundStyle(.tertiary)
141
147
  }
142
148
  }
@@ -3,7 +3,8 @@ import Charts
3
3
  import RunicCore
4
4
  import SwiftUI
5
5
 
6
- /// Overview showing all enabled providers at a glance — usage bars, today's tokens, and a combined chart.
6
+ /// Overview dashboard showing all enabled providers at a glance.
7
+ /// Inspired by Apple Activity app rings, Awwwards dashboard layouts, and Tokex stat cards.
7
8
  @MainActor
8
9
  struct OverviewMenuView: View {
9
10
  struct ProviderSummary: Identifiable {
@@ -15,6 +16,8 @@ struct OverviewMenuView: View {
15
16
  let todayTokens: Int
16
17
  let brandColor: Color
17
18
  let resetDescription: String?
19
+ let windowLabel: String? // e.g. "5h" or "Weekly"
20
+ let topModelContext: String? // e.g. "200K ctx"
18
21
  }
19
22
 
20
23
  struct DailyPoint: Identifiable {
@@ -22,82 +25,98 @@ struct OverviewMenuView: View {
22
25
  let date: Date
23
26
  let tokens: Int
24
27
  let provider: String
28
+ let color: Color
25
29
  }
26
30
 
27
31
  let summaries: [ProviderSummary]
28
32
  let chartPoints: [DailyPoint]
29
33
  let totalTodayTokens: Int
34
+ let totalProviders: Int
30
35
  let width: CGFloat
31
36
 
32
37
  var body: some View {
33
38
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
34
- // Header
35
- HStack(alignment: .firstTextBaseline) {
36
- Text("Overview")
37
- .font(.system(.title3, design: .rounded))
38
- .fontWeight(.bold)
39
+ // MARK: - Hero header
40
+
41
+ HStack(alignment: .center, spacing: RunicSpacing.xs) {
42
+ // Ring indicator showing overall usage
43
+ ZStack {
44
+ Circle()
45
+ .stroke(Color(nsColor: .separatorColor).opacity(0.15), lineWidth: 3)
46
+ Circle()
47
+ .trim(from: 0, to: min(1, self.averageUsedPercent / 100))
48
+ .stroke(
49
+ AngularGradient(
50
+ colors: [RunicColors.chartColor(at: 4), RunicColors.chartColor(at: 0)],
51
+ center: .center),
52
+ style: StrokeStyle(lineWidth: 3, lineCap: .round))
53
+ .rotationEffect(.degrees(-90))
54
+ }
55
+ .frame(width: 32, height: 32)
56
+
57
+ VStack(alignment: .leading, spacing: 0) {
58
+ Text(UsageFormatter.tokenCountString(self.totalTodayTokens))
59
+ .font(RunicFont.system(size: 22, weight: .bold))
60
+ Text("\(self.summaries.count) of \(self.totalProviders) active")
61
+ .font(RunicFont.caption2)
62
+ .foregroundStyle(.tertiary)
63
+ }
64
+
39
65
  Spacer()
40
- if self.totalTodayTokens > 0 {
41
- Text("\(UsageFormatter.tokenCountString(self.totalTodayTokens)) today")
42
- .font(.system(.caption, design: .rounded))
66
+
67
+ VStack(alignment: .trailing, spacing: 2) {
68
+ Text("today")
69
+ .font(RunicFont.caption2)
70
+ .foregroundStyle(.tertiary)
71
+ Text("\(Int(self.averageUsedPercent))% avg")
72
+ .font(RunicFont.caption)
73
+ .fontWeight(.medium)
43
74
  .foregroundStyle(.secondary)
44
75
  }
45
76
  }
46
77
 
47
- // Provider rows
78
+ Divider().opacity(0.5)
79
+
80
+ // MARK: - Provider cards
81
+
48
82
  if self.summaries.isEmpty {
49
- Text("No providers enabled.")
50
- .font(.system(.footnote, design: .rounded))
83
+ Text("No active providers.")
84
+ .font(RunicFont.footnote)
51
85
  .foregroundStyle(.secondary)
86
+ .frame(maxWidth: .infinity, alignment: .center)
87
+ .padding(.vertical, RunicSpacing.md)
52
88
  } else {
53
- VStack(spacing: RunicSpacing.xs) {
89
+ VStack(spacing: RunicSpacing.compact) {
54
90
  ForEach(self.summaries) { summary in
55
- HStack(spacing: RunicSpacing.xs) {
56
- // Provider icon
57
- if let icon = summary.icon {
58
- Image(nsImage: icon)
59
- .resizable()
60
- .aspectRatio(contentMode: .fit)
61
- .frame(width: 16, height: 16)
62
- }
63
-
64
- // Name
65
- Text(summary.name)
66
- .font(.system(.caption, design: .rounded))
67
- .fontWeight(.medium)
68
- .frame(width: 60, alignment: .leading)
69
- .lineLimit(1)
70
-
71
- // Mini progress bar
72
- GeometryReader { geo in
73
- ZStack(alignment: .leading) {
74
- Capsule()
75
- .fill(Color(nsColor: .separatorColor).opacity(0.2))
76
- Capsule()
77
- .fill(summary.brandColor)
78
- .frame(width: max(0, geo.size.width * min(1, summary.usedPercent / 100)))
79
- }
80
- }
81
- .frame(height: 6)
82
-
83
- // Percentage
84
- Text("\(Int(summary.usedPercent))%")
85
- .font(.system(size: 10, weight: .medium, design: .rounded))
86
- .foregroundStyle(.secondary)
87
- .frame(width: 30, alignment: .trailing)
88
- }
91
+ ProviderRow(summary: summary)
89
92
  }
90
93
  }
91
94
  }
92
95
 
93
- // Combined 7-day chart
96
+ // MARK: - Combined 7-day chart
97
+
94
98
  if !self.chartPoints.isEmpty {
95
- Divider()
96
- .padding(.vertical, RunicSpacing.xxxs)
99
+ Divider().opacity(0.5)
97
100
 
98
- Text("Last 7 days")
99
- .font(.system(.caption2, design: .rounded))
100
- .foregroundStyle(.tertiary)
101
+ HStack {
102
+ Text("7-day activity")
103
+ .font(RunicFont.caption2)
104
+ .foregroundStyle(.tertiary)
105
+ Spacer()
106
+ // Mini legend dots
107
+ HStack(spacing: RunicSpacing.xxs) {
108
+ ForEach(self.summaries.prefix(4)) { s in
109
+ Circle()
110
+ .fill(s.brandColor)
111
+ .frame(width: 5, height: 5)
112
+ }
113
+ if self.summaries.count > 4 {
114
+ Text("+\(self.summaries.count - 4)")
115
+ .font(RunicFont.system(size: 8))
116
+ .foregroundStyle(.quaternary)
117
+ }
118
+ }
119
+ }
101
120
 
102
121
  Chart {
103
122
  ForEach(self.chartPoints) { point in
@@ -105,29 +124,32 @@ struct OverviewMenuView: View {
105
124
  x: .value("Date", point.date, unit: .day),
106
125
  y: .value("Tokens", point.tokens))
107
126
  .foregroundStyle(by: .value("Provider", point.provider))
127
+ .cornerRadius(2)
108
128
  }
109
129
  }
110
130
  .chartForegroundStyleScale(range: self.chartColors)
111
131
  .chartLegend(.hidden)
112
132
  .chartYAxis {
113
133
  AxisMarks(position: .trailing, values: .automatic(desiredCount: 3)) { value in
134
+ AxisGridLine(stroke: StrokeStyle(lineWidth: 0.3, dash: [3, 3]))
135
+ .foregroundStyle(Color(nsColor: .separatorColor).opacity(0.3))
114
136
  AxisValueLabel {
115
137
  if let tokens = value.as(Int.self) {
116
138
  Text(UsageFormatter.tokenCountString(tokens))
117
- .font(.system(size: 8, design: .rounded))
139
+ .font(RunicFont.system(size: 8))
118
140
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
119
141
  }
120
142
  }
121
143
  }
122
144
  }
123
145
  .chartXAxis {
124
- AxisMarks(values: .automatic(desiredCount: 5)) { _ in
125
- AxisValueLabel(format: .dateTime.weekday(.short))
126
- .font(.system(size: 8, design: .rounded))
146
+ AxisMarks(values: .automatic(desiredCount: 7)) { _ in
147
+ AxisValueLabel(format: .dateTime.weekday(.narrow))
148
+ .font(RunicFont.system(size: 8, weight: .medium))
127
149
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
128
150
  }
129
151
  }
130
- .frame(height: 70)
152
+ .frame(height: 80)
131
153
  }
132
154
  }
133
155
  .padding(.horizontal, MenuCardMetrics.horizontalPadding)
@@ -135,7 +157,125 @@ struct OverviewMenuView: View {
135
157
  .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading)
136
158
  }
137
159
 
160
+ private var averageUsedPercent: Double {
161
+ let active = self.summaries.filter { $0.usedPercent > 0 }
162
+ guard !active.isEmpty else { return 0 }
163
+ return active.reduce(0) { $0 + $1.usedPercent } / Double(active.count)
164
+ }
165
+
138
166
  private var chartColors: [Color] {
139
167
  self.summaries.map(\.brandColor)
140
168
  }
141
169
  }
170
+
171
+ // MARK: - Provider row
172
+
173
+ private struct ProviderRow: View {
174
+ let summary: OverviewMenuView.ProviderSummary
175
+
176
+ var body: some View {
177
+ HStack(spacing: RunicSpacing.xs) {
178
+ // Icon with brand tint
179
+ if let icon = self.summary.icon {
180
+ Image(nsImage: icon)
181
+ .resizable()
182
+ .aspectRatio(contentMode: .fit)
183
+ .frame(width: 14, height: 14)
184
+ }
185
+
186
+ // Name
187
+ Text(self.summary.name)
188
+ .font(RunicFont.caption)
189
+ .fontWeight(.medium)
190
+ .frame(width: 58, alignment: .leading)
191
+ .lineLimit(1)
192
+
193
+ // Progress bar with gradient fill
194
+ GeometryReader { geo in
195
+ let fillWidth = max(0, geo.size.width * min(1, self.summary.usedPercent / 100))
196
+ ZStack(alignment: .leading) {
197
+ // Track
198
+ Capsule()
199
+ .fill(Color(nsColor: .separatorColor).opacity(0.12))
200
+
201
+ // Fill with gradient
202
+ Capsule()
203
+ .fill(
204
+ LinearGradient(
205
+ colors: [
206
+ self.summary.brandColor.opacity(0.9),
207
+ self.summary.brandColor,
208
+ ],
209
+ startPoint: .leading,
210
+ endPoint: .trailing))
211
+ .frame(width: fillWidth)
212
+
213
+ // Gloss overlay on fill
214
+ Capsule()
215
+ .fill(
216
+ LinearGradient(
217
+ stops: [
218
+ .init(color: .white.opacity(0.2), location: 0),
219
+ .init(color: .clear, location: 0.5),
220
+ ],
221
+ startPoint: .top,
222
+ endPoint: .bottom))
223
+ .frame(width: fillWidth)
224
+ }
225
+ }
226
+ .frame(height: 7)
227
+
228
+ // Percentage
229
+ Text("\(Int(self.summary.usedPercent))%")
230
+ .font(RunicFont.system(size: 9, weight: .semibold))
231
+ .foregroundStyle(self.summary.usedPercent > 80 ? .primary : .secondary)
232
+ .frame(width: 28, alignment: .trailing)
233
+
234
+ // Today's tokens (if any)
235
+ if self.summary.todayTokens > 0 {
236
+ Text(UsageFormatter.tokenCountString(self.summary.todayTokens))
237
+ .font(RunicFont.system(size: 8))
238
+ .foregroundStyle(.tertiary)
239
+ .frame(width: 32, alignment: .trailing)
240
+ }
241
+ }
242
+
243
+ // Second line: window + reset + context
244
+ let hasSecondLine = self.summary.windowLabel != nil ||
245
+ self.summary.resetDescription != nil ||
246
+ self.summary.topModelContext != nil
247
+
248
+ if hasSecondLine {
249
+ HStack(spacing: RunicSpacing.xxs) {
250
+ // Spacer for icon + name width
251
+ Color.clear.frame(width: 14 + 58 + RunicSpacing.xs * 2, height: 0)
252
+
253
+ if let window = self.summary.windowLabel {
254
+ InfoPill(text: window)
255
+ }
256
+ if let reset = self.summary.resetDescription {
257
+ InfoPill(text: reset)
258
+ }
259
+ if let ctx = self.summary.topModelContext {
260
+ InfoPill(text: ctx)
261
+ }
262
+ Spacer()
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ private struct InfoPill: View {
269
+ let text: String
270
+
271
+ var body: some View {
272
+ Text(self.text)
273
+ .font(RunicFont.system(size: 8, weight: .medium))
274
+ .foregroundStyle(.tertiary)
275
+ .padding(.horizontal, 4)
276
+ .padding(.vertical, 1)
277
+ .background(
278
+ Capsule(style: .continuous)
279
+ .fill(Color(nsColor: .controlBackgroundColor).opacity(RunicColors.Opacity.subtle)))
280
+ }
281
+ }
@@ -20,16 +20,18 @@ struct ProjectBreakdownMenuView: View {
20
20
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
21
21
  if model.items.isEmpty {
22
22
  Text("No project data.")
23
- .font(.footnote)
23
+ .font(RunicFont.footnote)
24
24
  .foregroundStyle(.secondary)
25
25
  } else {
26
26
  // MARK: - Title
27
+
27
28
  Text("Projects")
28
- .font(.caption)
29
+ .font(RunicFont.caption)
29
30
  .fontWeight(.semibold)
30
31
  .foregroundStyle(.primary)
31
32
 
32
33
  // MARK: - Horizontal bar chart
34
+
33
35
  Chart {
34
36
  ForEach(model.chartItems) { item in
35
37
  BarMark(
@@ -43,7 +45,7 @@ struct ProjectBreakdownMenuView: View {
43
45
  .chartYAxis {
44
46
  AxisMarks { _ in
45
47
  AxisValueLabel()
46
- .font(.caption2)
48
+ .font(RunicFont.caption2)
47
49
  .foregroundStyle(Color(nsColor: .secondaryLabelColor))
48
50
  }
49
51
  }
@@ -53,6 +55,7 @@ struct ProjectBreakdownMenuView: View {
53
55
  .accessibilityLabel(Self.chartAccessibilityLabel(model: model))
54
56
 
55
57
  // MARK: - Project list
58
+
56
59
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
57
60
  ForEach(model.chartItems) { item in
58
61
  Self.projectRow(item: item)
@@ -60,13 +63,15 @@ struct ProjectBreakdownMenuView: View {
60
63
  }
61
64
 
62
65
  // MARK: - Overflow note
66
+
63
67
  if model.overflowCount > 0 {
64
68
  Text("and \(model.overflowCount) more")
65
- .font(.caption2)
69
+ .font(RunicFont.caption2)
66
70
  .foregroundStyle(.tertiary)
67
71
  }
68
72
 
69
73
  // MARK: - Total summary
74
+
70
75
  Divider()
71
76
  Self.totalRow(model: model)
72
77
  }
@@ -84,24 +89,24 @@ struct ProjectBreakdownMenuView: View {
84
89
  .fill(item.color)
85
90
  .frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
86
91
  Text(item.name)
87
- .font(.caption)
92
+ .font(RunicFont.caption)
88
93
  .foregroundStyle(.primary)
89
94
  .lineLimit(1)
90
95
  .truncationMode(.middle)
91
96
  Spacer(minLength: RunicSpacing.xxs)
92
97
  VStack(alignment: .trailing, spacing: 0) {
93
98
  Text("\(UsageFormatter.tokenCountString(item.totalTokens)) tokens")
94
- .font(.system(.caption2, design: .rounded))
99
+ .font(RunicFont.caption2)
95
100
  .foregroundStyle(.secondary)
96
101
  HStack(spacing: RunicSpacing.xxxs) {
97
102
  if let cost = item.costText {
98
103
  Text(cost)
99
- .font(.system(.caption2, design: .rounded))
104
+ .font(RunicFont.caption2)
100
105
  .foregroundStyle(.tertiary)
101
106
  }
102
107
  if item.modelCount > 0 {
103
108
  Text("\(item.modelCount) model\(item.modelCount == 1 ? "" : "s")")
104
- .font(.system(.caption2, design: .rounded))
109
+ .font(RunicFont.caption2)
105
110
  .foregroundStyle(.tertiary)
106
111
  }
107
112
  }
@@ -112,17 +117,17 @@ struct ProjectBreakdownMenuView: View {
112
117
  private static func totalRow(model: ProjectModel) -> some View {
113
118
  HStack {
114
119
  Text("Total")
115
- .font(.caption)
120
+ .font(RunicFont.caption)
116
121
  .fontWeight(.medium)
117
122
  .foregroundStyle(.primary)
118
123
  Spacer(minLength: RunicSpacing.xxs)
119
124
  VStack(alignment: .trailing, spacing: 0) {
120
125
  Text("\(UsageFormatter.tokenCountString(model.grandTotalTokens)) tokens")
121
- .font(.system(.caption2, design: .rounded))
126
+ .font(RunicFont.caption2)
122
127
  .foregroundStyle(.secondary)
123
128
  if let cost = model.grandTotalCostText {
124
129
  Text(cost)
125
- .font(.system(.caption2, design: .rounded))
130
+ .font(RunicFont.caption2)
126
131
  .foregroundStyle(.tertiary)
127
132
  }
128
133
  }