@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
@@ -14,11 +14,11 @@ struct ProjectBudgetMenuView: View {
14
14
 
15
15
  var statusColor: Color {
16
16
  if self.isOverBudget {
17
- return Color(red: 0.94, green: 0.36, blue: 0.36) // Red
17
+ Color(red: 0.94, green: 0.36, blue: 0.36) // Red
18
18
  } else if self.isNearLimit {
19
- return Color(red: 0.94, green: 0.74, blue: 0.26) // Yellow
19
+ Color(red: 0.94, green: 0.74, blue: 0.26) // Yellow
20
20
  } else {
21
- return Color(red: 0.46, green: 0.75, blue: 0.36) // Green
21
+ Color(red: 0.46, green: 0.75, blue: 0.36) // Green
22
22
  }
23
23
  }
24
24
  }
@@ -44,7 +44,7 @@ struct ProjectBudgetMenuView: View {
44
44
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
45
45
  HStack {
46
46
  Text("Project Budgets")
47
- .font(.headline)
47
+ .font(RunicFont.headline)
48
48
  .fontWeight(.semibold)
49
49
  Spacer()
50
50
  Button {
@@ -52,9 +52,9 @@ struct ProjectBudgetMenuView: View {
52
52
  } label: {
53
53
  HStack(spacing: RunicSpacing.xxs) {
54
54
  Image(systemName: "gearshape")
55
- .font(.caption)
55
+ .font(RunicFont.caption)
56
56
  Text("Configure")
57
- .font(.caption)
57
+ .font(RunicFont.caption)
58
58
  }
59
59
  }
60
60
  .buttonStyle(.plain)
@@ -64,13 +64,13 @@ struct ProjectBudgetMenuView: View {
64
64
  if model.isEmpty {
65
65
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
66
66
  Text("No budgets configured.")
67
- .font(.footnote)
67
+ .font(RunicFont.footnote)
68
68
  .foregroundStyle(.secondary)
69
69
  Button {
70
70
  self.onOpenPreferences()
71
71
  } label: {
72
72
  Text("Set up budgets in Preferences")
73
- .font(.caption)
73
+ .font(RunicFont.caption)
74
74
  }
75
75
  .buttonStyle(.plain)
76
76
  .foregroundStyle(.blue)
@@ -88,8 +88,8 @@ struct ProjectBudgetMenuView: View {
88
88
  }
89
89
 
90
90
  // Summary
91
- let overBudget = model.filter { $0.isOverBudget }.count
92
- let nearLimit = model.filter { $0.isNearLimit && !$0.isOverBudget }.count
91
+ let overBudget = model.count(where: { $0.isOverBudget })
92
+ let nearLimit = model.count(where: { $0.isNearLimit && !$0.isOverBudget })
93
93
  if overBudget > 0 || nearLimit > 0 {
94
94
  Divider()
95
95
  .padding(.vertical, RunicSpacing.xxs)
@@ -98,20 +98,20 @@ struct ProjectBudgetMenuView: View {
98
98
  if overBudget > 0 {
99
99
  HStack(spacing: RunicSpacing.xxs) {
100
100
  Image(systemName: "exclamationmark.triangle.fill")
101
- .font(.caption2)
101
+ .font(RunicFont.caption2)
102
102
  .foregroundStyle(Color(red: 0.94, green: 0.36, blue: 0.36))
103
103
  Text("\(overBudget) project\(overBudget == 1 ? "" : "s") over budget")
104
- .font(.caption)
104
+ .font(RunicFont.caption)
105
105
  .foregroundStyle(.secondary)
106
106
  }
107
107
  }
108
108
  if nearLimit > 0 {
109
109
  HStack(spacing: RunicSpacing.xxs) {
110
110
  Image(systemName: "exclamationmark.circle.fill")
111
- .font(.caption2)
111
+ .font(RunicFont.caption2)
112
112
  .foregroundStyle(Color(red: 0.94, green: 0.74, blue: 0.26))
113
113
  Text("\(nearLimit) project\(nearLimit == 1 ? "" : "s") near limit")
114
- .font(.caption)
114
+ .font(RunicFont.caption)
115
115
  .foregroundStyle(.secondary)
116
116
  }
117
117
  }
@@ -167,34 +167,34 @@ private struct ProjectBudgetRow: View {
167
167
  var body: some View {
168
168
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
169
169
  HStack(alignment: .firstTextBaseline) {
170
- Text(project.projectName)
171
- .font(.body)
170
+ Text(self.project.projectName)
171
+ .font(RunicFont.body)
172
172
  .fontWeight(.medium)
173
173
  .lineLimit(1)
174
174
  Spacer()
175
175
  Text(self.budgetText)
176
- .font(.caption)
176
+ .font(RunicFont.caption)
177
177
  .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
178
178
  }
179
179
 
180
180
  UsageProgressBar(
181
- percent: project.percentUsed,
182
- tint: project.statusColor,
181
+ percent: self.project.percentUsed,
182
+ tint: self.project.statusColor,
183
183
  accessibilityLabel: "Budget usage")
184
184
 
185
185
  HStack(alignment: .firstTextBaseline) {
186
- Text(String(format: "%.0f%%", project.percentUsed))
187
- .font(.caption)
186
+ Text(String(format: "%.0f%%", self.project.percentUsed))
187
+ .font(RunicFont.caption)
188
188
  .foregroundStyle(self.statusTextColor)
189
189
  Spacer()
190
- if project.isOverBudget {
190
+ if self.project.isOverBudget {
191
191
  Text("Over budget")
192
- .font(.caption)
192
+ .font(RunicFont.caption)
193
193
  .foregroundStyle(Color(red: 0.94, green: 0.36, blue: 0.36))
194
194
  .fontWeight(.medium)
195
- } else if project.isNearLimit {
195
+ } else if self.project.isNearLimit {
196
196
  Text("Near limit")
197
- .font(.caption)
197
+ .font(RunicFont.caption)
198
198
  .foregroundStyle(Color(red: 0.94, green: 0.74, blue: 0.26))
199
199
  .fontWeight(.medium)
200
200
  }
@@ -208,13 +208,13 @@ private struct ProjectBudgetRow: View {
208
208
  }
209
209
 
210
210
  private var budgetText: String {
211
- "\(UsageFormatter.usdString(project.spent)) / \(UsageFormatter.usdString(project.budget.monthlyLimit))"
211
+ "\(UsageFormatter.usdString(self.project.spent)) / \(UsageFormatter.usdString(self.project.budget.monthlyLimit))"
212
212
  }
213
213
 
214
214
  private var statusTextColor: Color {
215
215
  if self.isHighlighted {
216
216
  return MenuHighlightStyle.selectionText
217
217
  }
218
- return project.statusColor
218
+ return self.project.statusColor
219
219
  }
220
220
  }
@@ -33,11 +33,11 @@ struct ProviderTabBarView: View {
33
33
  .aspectRatio(contentMode: .fit)
34
34
  .frame(width: 18, height: 18)
35
35
  } else if tab.provider == nil {
36
- Image(systemName: "square.grid.2x2")
36
+ Image(systemName: "square.grid.2x2")
37
37
  .font(.system(size: 14, weight: .medium))
38
38
  }
39
39
  Text(tab.label)
40
- .font(.system(.caption, design: .rounded))
40
+ .font(RunicFont.caption)
41
41
  .fontWeight(tab.isSelected ? .semibold : .regular)
42
42
  .lineLimit(1)
43
43
  }
@@ -47,16 +47,14 @@ struct ProviderTabBarView: View {
47
47
  Capsule(style: .continuous)
48
48
  .fill(tab.isSelected
49
49
  ? tab.brandColor.opacity(RunicColors.Opacity.medium)
50
- : Color(nsColor: .controlBackgroundColor).opacity(RunicColors.Opacity.subtle))
51
- )
50
+ : Color(nsColor: .controlBackgroundColor).opacity(RunicColors.Opacity.subtle)))
52
51
  .overlay(
53
52
  Capsule(style: .continuous)
54
53
  .stroke(
55
54
  tab.isSelected
56
55
  ? tab.brandColor.opacity(RunicColors.Opacity.strong)
57
56
  : Color(nsColor: .separatorColor).opacity(RunicColors.Opacity.light),
58
- lineWidth: 0.5)
59
- )
57
+ lineWidth: 0.5))
60
58
  .foregroundStyle(tab.isSelected ? tab.brandColor : .secondary)
61
59
  .shadow(
62
60
  color: tab.isSelected ? tab.brandColor.opacity(0.15) : .clear,
@@ -38,7 +38,7 @@ struct SubscriptionUtilizationChartMenuView: View {
38
38
  self.width = width
39
39
  }
40
40
 
41
- private static let barColor = RunicColors.chartColor(at: 4) // teal
41
+ private static let barColor = RunicColors.chartColor(at: 4) // teal
42
42
  private static let wastedColor = Color(nsColor: .systemGray).opacity(0.2)
43
43
 
44
44
  var body: some View {
@@ -50,7 +50,7 @@ struct SubscriptionUtilizationChartMenuView: View {
50
50
 
51
51
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
52
52
  Text("Utilization")
53
- .font(.system(.subheadline, design: .rounded))
53
+ .font(RunicFont.subheadline)
54
54
  .fontWeight(.semibold)
55
55
 
56
56
  Picker("", selection: self.$selectedPeriod) {
@@ -62,7 +62,7 @@ struct SubscriptionUtilizationChartMenuView: View {
62
62
 
63
63
  if model.bars.isEmpty {
64
64
  Text("No utilization data available.")
65
- .font(.system(.footnote, design: .rounded))
65
+ .font(RunicFont.footnote)
66
66
  .foregroundStyle(.secondary)
67
67
  .frame(height: 80)
68
68
  } else {
@@ -83,7 +83,7 @@ struct SubscriptionUtilizationChartMenuView: View {
83
83
  AxisValueLabel {
84
84
  if let pct = value.as(Int.self) {
85
85
  Text("\(pct)%")
86
- .font(.system(.caption2, design: .rounded))
86
+ .font(RunicFont.caption2)
87
87
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
88
88
  }
89
89
  }
@@ -92,7 +92,7 @@ struct SubscriptionUtilizationChartMenuView: View {
92
92
  .chartXAxis {
93
93
  AxisMarks(values: .automatic(desiredCount: 5)) { _ in
94
94
  AxisValueLabel()
95
- .font(.system(.caption2, design: .rounded))
95
+ .font(RunicFont.caption2)
96
96
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
97
97
  }
98
98
  }
@@ -104,7 +104,7 @@ struct SubscriptionUtilizationChartMenuView: View {
104
104
  let used = Int(latest.usedPercent.rounded())
105
105
  let unused = max(0, 100 - used)
106
106
  Text("\(latest.label): \(used)% used, \(unused)% unused")
107
- .font(.system(.caption, design: .rounded))
107
+ .font(RunicFont.caption)
108
108
  .foregroundStyle(.secondary)
109
109
  }
110
110
  }
@@ -159,7 +159,8 @@ struct SubscriptionUtilizationChartMenuView: View {
159
159
  // Aggregate by ISO week
160
160
  var weekBuckets: [(label: String, tokens: Int, date: Date, isThisWeek: Bool)] = []
161
161
  for weekOffset in stride(from: -3, through: 0, by: 1) {
162
- guard let weekStart = calendar.date(byAdding: .weekOfYear, value: weekOffset, to: todayStart) else { continue }
162
+ guard let weekStart = calendar.date(byAdding: .weekOfYear, value: weekOffset, to: todayStart)
163
+ else { continue }
163
164
  var total = 0
164
165
  for dayOffset in 0..<7 {
165
166
  guard let date = calendar.date(byAdding: .day, value: dayOffset, to: weekStart) else { continue }
@@ -171,20 +172,27 @@ struct SubscriptionUtilizationChartMenuView: View {
171
172
  let maxWeekTokens = weekBuckets.map(\.tokens).max() ?? 1
172
173
  let bars = weekBuckets.map { bucket in
173
174
  let pct = maxWeekTokens > 0 ? Double(bucket.tokens) / Double(maxWeekTokens) * 100 : 0
174
- return UtilizationBar(id: bucket.label, label: bucket.label, date: bucket.date, usedPercent: pct, isToday: bucket.isThisWeek)
175
+ return UtilizationBar(
176
+ id: bucket.label,
177
+ label: bucket.label,
178
+ date: bucket.date,
179
+ usedPercent: pct,
180
+ isToday: bucket.isThisWeek)
175
181
  }
176
182
  return UtilizationModel(bars: bars)
177
183
 
178
184
  case .monthly:
179
185
  var monthBuckets: [(label: String, tokens: Int, date: Date, isThisMonth: Bool)] = []
180
186
  for monthOffset in stride(from: -2, through: 0, by: 1) {
181
- guard let monthStart = calendar.date(byAdding: .month, value: monthOffset, to: todayStart) else { continue }
187
+ guard let monthStart = calendar.date(byAdding: .month, value: monthOffset, to: todayStart)
188
+ else { continue }
182
189
  let monthComponents = calendar.dateComponents([.year, .month], from: monthStart)
183
190
  guard let actualMonthStart = calendar.date(from: monthComponents),
184
191
  let range = calendar.range(of: .day, in: .month, for: actualMonthStart) else { continue }
185
192
  var total = 0
186
193
  for dayOffset in 0..<range.count {
187
- guard let date = calendar.date(byAdding: .day, value: dayOffset, to: actualMonthStart) else { continue }
194
+ guard let date = calendar.date(byAdding: .day, value: dayOffset, to: actualMonthStart)
195
+ else { continue }
188
196
  total += tokensByDay[formatter.string(from: date)] ?? 0
189
197
  }
190
198
  let label = monthStart.formatted(.dateTime.month(.abbreviated))
@@ -193,7 +201,12 @@ struct SubscriptionUtilizationChartMenuView: View {
193
201
  let maxMonthTokens = monthBuckets.map(\.tokens).max() ?? 1
194
202
  let bars = monthBuckets.map { bucket in
195
203
  let pct = maxMonthTokens > 0 ? Double(bucket.tokens) / Double(maxMonthTokens) * 100 : 0
196
- return UtilizationBar(id: bucket.label, label: bucket.label, date: bucket.date, usedPercent: pct, isToday: bucket.isThisMonth)
204
+ return UtilizationBar(
205
+ id: bucket.label,
206
+ label: bucket.label,
207
+ date: bucket.date,
208
+ usedPercent: pct,
209
+ isToday: bucket.isThisMonth)
197
210
  }
198
211
  return UtilizationModel(bars: bars)
199
212
  }
@@ -32,7 +32,7 @@ struct UsageBreakdownChartMenuView: View {
32
32
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
33
33
  if model.points.isEmpty {
34
34
  Text("No usage breakdown data.")
35
- .font(.footnote)
35
+ .font(RunicFont.footnote)
36
36
  .foregroundStyle(.secondary)
37
37
  } else {
38
38
  Chart {
@@ -58,7 +58,7 @@ struct UsageBreakdownChartMenuView: View {
58
58
  AxisGridLine().foregroundStyle(Color.clear)
59
59
  AxisTick().foregroundStyle(Color.clear)
60
60
  AxisValueLabel(format: .dateTime.month(.abbreviated).day())
61
- .font(.caption2)
61
+ .font(RunicFont.caption2)
62
62
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
63
63
  }
64
64
  }
@@ -88,13 +88,13 @@ struct UsageBreakdownChartMenuView: View {
88
88
  let detail = self.detailLines(model: model)
89
89
  VStack(alignment: .leading, spacing: 0) {
90
90
  Text(detail.primary)
91
- .font(.system(.caption, design: .rounded))
91
+ .font(RunicFont.caption)
92
92
  .foregroundStyle(.secondary)
93
93
  .lineLimit(1)
94
94
  .truncationMode(.tail)
95
95
  .frame(height: 16, alignment: .leading)
96
96
  Text(detail.secondary ?? " ")
97
- .font(.system(.caption, design: .rounded))
97
+ .font(RunicFont.caption)
98
98
  .foregroundStyle(.secondary)
99
99
  .lineLimit(1)
100
100
  .truncationMode(.tail)
@@ -113,7 +113,7 @@ struct UsageBreakdownChartMenuView: View {
113
113
  .fill(model.color(for: service))
114
114
  .frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
115
115
  Text(service)
116
- .font(.caption2)
116
+ .font(RunicFont.caption2)
117
117
  .foregroundStyle(.secondary)
118
118
  .lineLimit(1)
119
119
  }
@@ -46,12 +46,12 @@ struct UsageHeatmapMenuView: View {
46
46
  let model = Self.makeModel(from: self.hourlySummaries)
47
47
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
48
48
  Text("Usage Heatmap (24×7)")
49
- .font(.headline)
49
+ .font(RunicFont.headline)
50
50
  .fontWeight(.semibold)
51
51
 
52
52
  if model.isEmpty {
53
53
  Text("No heatmap data.")
54
- .font(.footnote)
54
+ .font(RunicFont.footnote)
55
55
  .foregroundStyle(.secondary)
56
56
  } else {
57
57
  ScrollView(.horizontal, showsIndicators: true) {
@@ -62,7 +62,7 @@ struct UsageHeatmapMenuView: View {
62
62
  .frame(width: 40, alignment: .leading)
63
63
  ForEach(0..<24, id: \.self) { hour in
64
64
  Text(self.hourLabel(hour))
65
- .font(.caption2)
65
+ .font(RunicFont.caption2)
66
66
  .foregroundStyle(.secondary)
67
67
  .frame(width: 40, alignment: .center)
68
68
  .lineLimit(1)
@@ -73,7 +73,7 @@ struct UsageHeatmapMenuView: View {
73
73
  ForEach(0..<7, id: \.self) { weekday in
74
74
  HStack(spacing: 2) {
75
75
  Text(self.weekdayLabel(weekday))
76
- .font(.caption)
76
+ .font(RunicFont.caption)
77
77
  .foregroundStyle(.secondary)
78
78
  .frame(width: 38, alignment: .leading)
79
79
 
@@ -99,7 +99,7 @@ struct UsageHeatmapMenuView: View {
99
99
  // Legend
100
100
  HStack(spacing: RunicSpacing.xs) {
101
101
  Text("Low")
102
- .font(.caption2)
102
+ .font(RunicFont.caption2)
103
103
  .foregroundStyle(.secondary)
104
104
  HStack(spacing: 2) {
105
105
  ForEach(0..<5, id: \.self) { level in
@@ -110,7 +110,7 @@ struct UsageHeatmapMenuView: View {
110
110
  }
111
111
  }
112
112
  Text("High")
113
- .font(.caption2)
113
+ .font(RunicFont.caption2)
114
114
  .foregroundStyle(.secondary)
115
115
  }
116
116
  .padding(.top, RunicSpacing.xs)
@@ -121,7 +121,7 @@ struct UsageHeatmapMenuView: View {
121
121
 
122
122
  let detail = self.detailText(model: model)
123
123
  Text(detail)
124
- .font(.caption)
124
+ .font(RunicFont.caption)
125
125
  .foregroundStyle(.secondary)
126
126
  .lineLimit(2)
127
127
  .frame(height: 32, alignment: .leading)
@@ -229,4 +229,3 @@ private struct HeatmapCellView: View {
229
229
  .cornerRadius(RunicCornerRadius.xs)
230
230
  }
231
231
  }
232
-
@@ -5,30 +5,29 @@ import SwiftUI
5
5
  @MainActor
6
6
  struct UsageTimelineChartMenuView: View {
7
7
  enum TimeRange: String, CaseIterable {
8
- case oneHour = "1h"
9
- case sixHours = "6h"
10
- case oneDay = "1d"
8
+ case threeDays = "3d"
11
9
  case sevenDays = "7d"
12
10
  case thirtyDays = "30d"
11
+ case quarter = "90d"
12
+ case year = "1y"
13
13
 
14
14
  var cutoffInterval: TimeInterval {
15
15
  switch self {
16
- case .oneHour: return -3600
17
- case .sixHours: return -21600
18
- case .oneDay: return -86400
19
- case .sevenDays: return -604800
20
- case .thirtyDays: return -2592000
16
+ case .threeDays: -259_200
17
+ case .sevenDays: -604_800
18
+ case .thirtyDays: -2_592_000
19
+ case .quarter: -7_776_000
20
+ case .year: -31_536_000
21
21
  }
22
22
  }
23
23
 
24
24
  var usesHourlyData: Bool {
25
- switch self {
26
- case .oneHour, .sixHours, .oneDay: return true
27
- default: return false
28
- }
25
+ false
29
26
  }
30
27
 
31
- var label: String { self.rawValue }
28
+ var label: String {
29
+ self.rawValue
30
+ }
32
31
  }
33
32
 
34
33
  private struct Point: Identifiable {
@@ -48,7 +47,7 @@ struct UsageTimelineChartMenuView: View {
48
47
  private let dailySummaries: [UsageLedgerDailySummary]
49
48
  private let hourlySummaries: [UsageLedgerHourlySummary]
50
49
  private let width: CGFloat
51
- @State private var selectedTimeRange: TimeRange = .oneDay
50
+ @State private var selectedTimeRange: TimeRange = .sevenDays
52
51
  @State private var selectedKey: String?
53
52
 
54
53
  init(
@@ -65,14 +64,12 @@ struct UsageTimelineChartMenuView: View {
65
64
  private static let selectionBandColor = Color(nsColor: .labelColor).opacity(0.1)
66
65
 
67
66
  var body: some View {
68
- let model = self.selectedTimeRange.usesHourlyData
69
- ? Self.makeHourlyModel(from: self.hourlySummaries, timeRange: self.selectedTimeRange)
70
- : Self.makeDailyModel(from: self.dailySummaries, timeRange: self.selectedTimeRange)
67
+ let model = Self.makeDailyModel(from: self.dailySummaries, timeRange: self.selectedTimeRange)
71
68
 
72
69
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
73
70
  // Header on its own line
74
71
  Text("Timeline")
75
- .font(.system(.subheadline, design: .rounded))
72
+ .font(RunicFont.subheadline)
76
73
  .fontWeight(.semibold)
77
74
 
78
75
  // Range picker on its own line
@@ -85,7 +82,7 @@ struct UsageTimelineChartMenuView: View {
85
82
 
86
83
  if model.points.isEmpty {
87
84
  Text("No data for selected range.")
88
- .font(.system(.footnote, design: .rounded))
85
+ .font(RunicFont.footnote)
89
86
  .foregroundStyle(.secondary)
90
87
  .frame(height: 100)
91
88
  } else {
@@ -119,7 +116,7 @@ struct UsageTimelineChartMenuView: View {
119
116
  .symbolSize(60)
120
117
  .annotation(position: .top, spacing: 4) {
121
118
  Text("Peak")
122
- .font(.system(.caption2, design: .rounded))
119
+ .font(RunicFont.caption2)
123
120
  .foregroundStyle(Color(nsColor: .systemYellow))
124
121
  }
125
122
  }
@@ -131,7 +128,7 @@ struct UsageTimelineChartMenuView: View {
131
128
  AxisValueLabel {
132
129
  if let tokens = value.as(Int.self) {
133
130
  Text(UsageFormatter.tokenCountString(tokens))
134
- .font(.system(.caption2, design: .rounded))
131
+ .font(RunicFont.caption2)
135
132
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
136
133
  }
137
134
  }
@@ -142,7 +139,7 @@ struct UsageTimelineChartMenuView: View {
142
139
  AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5, dash: [4, 3]))
143
140
  .foregroundStyle(Color(nsColor: .separatorColor).opacity(0.3))
144
141
  AxisValueLabel(format: model.xAxisFormat)
145
- .font(.system(.caption2, design: .rounded))
142
+ .font(RunicFont.caption2)
146
143
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
147
144
  }
148
145
  }
@@ -170,7 +167,7 @@ struct UsageTimelineChartMenuView: View {
170
167
  // Detail line
171
168
  let detail = self.detailText(model: model)
172
169
  Text(detail)
173
- .font(.system(.caption, design: .rounded))
170
+ .font(RunicFont.caption)
174
171
  .foregroundStyle(.secondary)
175
172
  .lineLimit(1)
176
173
  .truncationMode(.tail)
@@ -191,7 +188,8 @@ struct UsageTimelineChartMenuView: View {
191
188
  }
192
189
  .chartPanelStyle(width: self.width)
193
190
  .accessibilityElement(children: .ignore)
194
- .accessibilityLabel("Usage timeline chart showing \(model.points.count) data points over \(self.selectedTimeRange.label)")
191
+ .accessibilityLabel(
192
+ "Usage timeline chart showing \(model.points.count) data points over \(self.selectedTimeRange.label)")
195
193
  }
196
194
 
197
195
  // MARK: - Stat cell
@@ -203,10 +201,10 @@ struct UsageTimelineChartMenuView: View {
203
201
  var body: some View {
204
202
  VStack(alignment: .leading, spacing: RunicSpacing.xxxs) {
205
203
  Text(self.label)
206
- .font(.system(.caption2, design: .rounded))
204
+ .font(RunicFont.caption2)
207
205
  .foregroundStyle(.tertiary)
208
206
  Text(self.value)
209
- .font(.system(.caption, design: .rounded))
207
+ .font(RunicFont.caption)
210
208
  .fontWeight(.medium)
211
209
  }
212
210
  }
@@ -261,10 +259,12 @@ struct UsageTimelineChartMenuView: View {
261
259
  }
262
260
  }
263
261
 
264
- let desiredAxisCount: Int = switch timeRange {
265
- case .oneHour, .sixHours, .oneDay: 4
262
+ let desiredAxisCount = switch timeRange {
263
+ case .threeDays: 3
266
264
  case .sevenDays: 5
267
265
  case .thirtyDays: 6
266
+ case .quarter: 6
267
+ case .year: 6
268
268
  }
269
269
 
270
270
  return Model(
@@ -281,54 +281,6 @@ struct UsageTimelineChartMenuView: View {
281
281
  isHourly: false)
282
282
  }
283
283
 
284
- // MARK: - Hourly model
285
-
286
- private static func makeHourlyModel(from summaries: [UsageLedgerHourlySummary], timeRange: TimeRange) -> Model {
287
- let now = Date()
288
- let cutoffDate = now.addingTimeInterval(timeRange.cutoffInterval)
289
- let filtered = summaries.filter { $0.hourStart >= cutoffDate }
290
- let sorted = filtered.sorted { $0.hourStart < $1.hourStart }
291
-
292
- var points: [Point] = []
293
- var pointsByKey: [String: Point] = [:]
294
- var dateKeys: [(key: String, date: Date)] = []
295
- var peak: Point?
296
- var totalTokens = 0
297
-
298
- for summary in sorted {
299
- let tokens = summary.totals.totalTokens
300
- totalTokens += tokens
301
- let point = Point(date: summary.hourStart, totalTokens: tokens, key: summary.hourKey)
302
- points.append(point)
303
- pointsByKey[summary.hourKey] = point
304
- dateKeys.append((summary.hourKey, summary.hourStart))
305
-
306
- if peak == nil || tokens > (peak?.totalTokens ?? 0) {
307
- peak = point
308
- }
309
- }
310
-
311
- let desiredAxisCount: Int = switch timeRange {
312
- case .oneHour: 4
313
- case .sixHours: 6
314
- case .oneDay: 8
315
- default: 6
316
- }
317
-
318
- return Model(
319
- points: points,
320
- pointsByKey: pointsByKey,
321
- dateKeys: dateKeys,
322
- peakPoint: peak,
323
- totalTokens: totalTokens,
324
- averagePerPeriod: points.isEmpty ? 0 : totalTokens / max(1, points.count),
325
- peakTokens: peak?.totalTokens ?? 0,
326
- totalCostUSD: nil,
327
- desiredAxisCount: desiredAxisCount,
328
- xAxisFormat: .dateTime.hour(.defaultDigits(amPM: .narrow)),
329
- isHourly: true)
330
- }
331
-
332
284
  // MARK: - Interaction
333
285
 
334
286
  private func selectionBandRect(model: Model, proxy: ChartProxy, geo: GeometryProxy) -> CGRect? {
@@ -49,12 +49,12 @@ struct UsageWindowComparisonChartMenuView: View {
49
49
 
50
50
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
51
51
  Text("Windows")
52
- .font(.system(.subheadline, design: .rounded))
52
+ .font(RunicFont.subheadline)
53
53
  .fontWeight(.semibold)
54
54
 
55
55
  if model.points.isEmpty {
56
56
  Text("No usage window data available.")
57
- .font(.system(.footnote, design: .rounded))
57
+ .font(RunicFont.footnote)
58
58
  .foregroundStyle(.secondary)
59
59
  .frame(height: 80)
60
60
  } else {
@@ -64,7 +64,8 @@ struct UsageWindowComparisonChartMenuView: View {
64
64
  x: .value("Date", point.date),
65
65
  y: .value("%", point.percent),
66
66
  series: .value("Window", point.series))
67
- .foregroundStyle(point.series == self.primaryLabel ? Self.primaryColor : Self.secondaryColor)
67
+ .foregroundStyle(point.series == self.primaryLabel ? Self.primaryColor : Self
68
+ .secondaryColor)
68
69
  .lineStyle(StrokeStyle(lineWidth: 2))
69
70
  .interpolationMethod(.catmullRom)
70
71
  }
@@ -77,7 +78,7 @@ struct UsageWindowComparisonChartMenuView: View {
77
78
  AxisValueLabel {
78
79
  if let pct = value.as(Int.self) {
79
80
  Text("\(pct)%")
80
- .font(.system(.caption2, design: .rounded))
81
+ .font(RunicFont.caption2)
81
82
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
82
83
  }
83
84
  }
@@ -88,7 +89,7 @@ struct UsageWindowComparisonChartMenuView: View {
88
89
  AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5, dash: [4, 3]))
89
90
  .foregroundStyle(Color(nsColor: .separatorColor).opacity(0.3))
90
91
  AxisValueLabel(format: .dateTime.month(.abbreviated).day())
91
- .font(.system(.caption2, design: .rounded))
92
+ .font(RunicFont.caption2)
92
93
  .foregroundStyle(Color(nsColor: .tertiaryLabelColor))
93
94
  }
94
95
  }
@@ -102,7 +103,7 @@ struct UsageWindowComparisonChartMenuView: View {
102
103
  .fill(Self.primaryColor)
103
104
  .frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
104
105
  Text(self.primaryLabel)
105
- .font(.system(.caption2, design: .rounded))
106
+ .font(RunicFont.caption2)
106
107
  .foregroundStyle(.secondary)
107
108
  }
108
109
  if let secondaryLabel = self.secondaryLabel {
@@ -111,7 +112,7 @@ struct UsageWindowComparisonChartMenuView: View {
111
112
  .fill(Self.secondaryColor)
112
113
  .frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
113
114
  Text(secondaryLabel)
114
- .font(.system(.caption2, design: .rounded))
115
+ .font(RunicFont.caption2)
115
116
  .foregroundStyle(.secondary)
116
117
  }
117
118
  }