@sriinnu/runic 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +8 -1
  3. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  4. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  5. package/RuniciOS/Views/ProviderListView.swift +10 -10
  6. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  7. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  8. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  9. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  10. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  11. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  12. package/Sources/Runic/About.swift +3 -3
  13. package/Sources/Runic/AppNotifications.swift +5 -3
  14. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
  15. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  16. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  17. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  18. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +18 -13
  19. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  20. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  21. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  22. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  23. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  24. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  25. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  26. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  28. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  29. package/Sources/Runic/Core/RunicColors.swift +6 -6
  30. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  31. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  32. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +1 -3
  33. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
  34. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
  35. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
  36. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
  37. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  38. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
  39. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
  40. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
  41. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
  42. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
  43. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
  44. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
  45. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
  46. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
  47. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
  48. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  49. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
  50. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
  51. package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
  52. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
  53. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  54. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  55. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  56. package/Sources/Runic/Core/Stores/UsageStore.swift +187 -161
  57. package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
  58. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
  59. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  60. package/Sources/Runic/ProviderRegistry.swift +1 -1
  61. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  62. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  63. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  81. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  83. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  84. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  85. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  87. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  88. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  107. package/Sources/Runic/RunicApp.swift +46 -36
  108. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  109. package/Sources/Runic/UsagePaceText.swift +1 -1
  110. package/Sources/Runic/UsageProgressBar.swift +13 -9
  111. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  112. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  113. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  114. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  115. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  116. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  117. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  118. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  119. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  120. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +12 -10
  121. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  122. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  123. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  124. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  125. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  126. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  127. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  128. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  129. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +18 -15
  130. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  131. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  132. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  133. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  134. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  135. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  136. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +22 -17
  137. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  138. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  139. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  140. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  141. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  142. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  143. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  144. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  145. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  146. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  147. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  148. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  149. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  150. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  151. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +44 -39
  152. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  153. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  154. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  155. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  156. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  157. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  158. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  159. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  160. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  161. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  162. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  163. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  164. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  165. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  166. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  167. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  168. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  169. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  170. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  171. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  172. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  173. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  174. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +73 -75
  175. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  176. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  177. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  178. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  179. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  180. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  181. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  182. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  183. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  184. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  185. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  186. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  187. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  188. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  189. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  190. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +3 -3
  191. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  192. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  193. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  194. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  195. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  196. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  197. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  198. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  199. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  200. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +1 -1
  201. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  202. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  204. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  205. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  206. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  207. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  208. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  209. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  210. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  211. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  212. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  213. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  214. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  215. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +2 -2
  216. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  217. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  218. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  219. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +5 -2
  220. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  221. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  222. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  225. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +7 -7
  226. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  227. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  228. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  229. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  230. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +23 -10
  231. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  232. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  233. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  234. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  235. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  236. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +26 -13
  237. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  240. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  241. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  242. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  243. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  244. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  245. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  246. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  247. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +48 -21
  248. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  249. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  250. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  251. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  252. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  253. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  254. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  255. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  256. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  257. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  258. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  259. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +27 -13
  260. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
  261. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
  262. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  263. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  264. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  265. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  266. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  267. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  268. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  269. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  270. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  271. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  272. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  273. package/Tests/RunicTests/CLICostTests.swift +6 -7
  274. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  275. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  276. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  277. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  278. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  279. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  280. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  281. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  282. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  283. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  284. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  285. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  286. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  287. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  288. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  289. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  290. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  291. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  292. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  293. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  294. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  295. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  296. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  297. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  298. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  299. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  300. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  301. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  302. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  303. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  304. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  305. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  306. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  307. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  308. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  309. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  310. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  311. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  312. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  313. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  314. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  315. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  316. package/Tests/RunicTests/RunicTests.swift +6 -7
  317. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  318. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  319. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  320. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  321. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  322. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  323. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  324. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  325. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  326. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  327. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  328. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  329. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  330. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  331. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  332. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  333. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  334. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  335. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  336. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  337. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  338. package/appcast.xml +36 -76
  339. package/package.json +1 -1
  340. package/runic.png +0 -0
  341. package/runic.svg +15 -0
  342. package/version.env +2 -2
@@ -61,14 +61,14 @@ struct SmallWidgetView: View {
61
61
  .frame(width: 8, height: 8)
62
62
 
63
63
  Text(provider.displayName)
64
- .font(.system(size: 13, weight: .semibold))
64
+ .font(RunicFont.system(size: 13, weight: .semibold))
65
65
  .foregroundColor(.white)
66
66
 
67
67
  Spacer()
68
68
 
69
69
  // Status indicator
70
70
  Image(systemName: statusIcon(for: provider))
71
- .font(.system(size: 12))
71
+ .font(RunicFont.system(size: 12))
72
72
  .foregroundColor(statusColor(for: provider))
73
73
  }
74
74
  .padding(.horizontal, 16)
@@ -85,19 +85,19 @@ struct SmallWidgetView: View {
85
85
  VStack(spacing: 2) {
86
86
  if let countdown = provider.resetCountdown {
87
87
  Text("Resets")
88
- .font(.system(size: 10, weight: .medium))
88
+ .font(RunicFont.system(size: 10, weight: .medium))
89
89
  .foregroundColor(.white.opacity(0.6))
90
90
 
91
91
  Text(countdown)
92
- .font(.system(size: 12, weight: .semibold, design: .rounded))
92
+ .font(RunicFont.system(size: 12, weight: .semibold))
93
93
  .foregroundColor(.white.opacity(0.9))
94
94
  } else {
95
95
  Text("Updated")
96
- .font(.system(size: 10, weight: .medium))
96
+ .font(RunicFont.system(size: 10, weight: .medium))
97
97
  .foregroundColor(.white.opacity(0.6))
98
98
 
99
99
  Text(relativeTime(from: provider.updatedAt))
100
- .font(.system(size: 11, weight: .medium))
100
+ .font(RunicFont.system(size: 11, weight: .medium))
101
101
  .foregroundColor(.white.opacity(0.8))
102
102
  }
103
103
  }
@@ -160,15 +160,15 @@ struct SmallWidgetView: View {
160
160
  private var emptyStateView: some View {
161
161
  VStack(spacing: 8) {
162
162
  Image(systemName: "exclamationmark.triangle")
163
- .font(.system(size: 24))
163
+ .font(RunicFont.system(size: 24))
164
164
  .foregroundColor(.white.opacity(0.5))
165
165
 
166
166
  Text("No Data")
167
- .font(.system(size: 14, weight: .semibold))
167
+ .font(RunicFont.system(size: 14, weight: .semibold))
168
168
  .foregroundColor(.white.opacity(0.7))
169
169
 
170
170
  Text("Open app to refresh")
171
- .font(.system(size: 11))
171
+ .font(RunicFont.system(size: 11))
172
172
  .foregroundColor(.white.opacity(0.5))
173
173
  .multilineTextAlignment(.center)
174
174
  }
@@ -24,9 +24,9 @@ func showAbout() {
24
24
  let description = NSAttributedString(
25
25
  string: "Runic tracks AI usage, tokens, and spend across providers from your menu bar.\n\n",
26
26
  attributes: [
27
- .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
28
- .foregroundColor: NSColor.secondaryLabelColor,
29
- ])
27
+ .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
28
+ .foregroundColor: NSColor.secondaryLabelColor,
29
+ ])
30
30
  let credits = NSMutableAttributedString(attributedString: description)
31
31
  credits.append(NSAttributedString(string: "MIT License\n", attributes: [
32
32
  .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  @preconcurrency import UserNotifications
4
4
 
5
5
  @MainActor
@@ -10,7 +10,9 @@ final class AppNotifications {
10
10
  private let logger = RunicLog.logger("notifications")
11
11
  private var authorizationTask: Task<Bool, Never>?
12
12
 
13
- init(centerProvider: @escaping @Sendable () -> UNUserNotificationCenter? = { AppNotifications.safeCurrentCenter() }) {
13
+ init(centerProvider: @escaping @Sendable ()
14
+ -> UNUserNotificationCenter? = { AppNotifications.safeCurrentCenter() })
15
+ {
14
16
  self.centerProvider = centerProvider
15
17
  }
16
18
 
@@ -60,7 +62,7 @@ final class AppNotifications {
60
62
  /// Safely obtain the current UNUserNotificationCenter, returning nil when the
61
63
  /// bundle proxy is not available (e.g. when running a bare executable outside
62
64
  /// a proper .app bundle).
63
- nonisolated private static func safeCurrentCenter() -> UNUserNotificationCenter? {
65
+ private nonisolated static func safeCurrentCenter() -> UNUserNotificationCenter? {
64
66
  guard Bundle.main.bundleIdentifier != nil else { return nil }
65
67
  return UNUserNotificationCenter.current()
66
68
  }
@@ -158,9 +158,13 @@ extension StatusItemController {
158
158
  }
159
159
  }
160
160
 
161
- @objc func showSettingsGeneral() { self.openSettings(tab: .general) }
161
+ @objc func showSettingsGeneral() {
162
+ self.openSettings(tab: .general)
163
+ }
162
164
 
163
- @objc func showSettingsAbout() { self.openSettings(tab: .about) }
165
+ @objc func showSettingsAbout() {
166
+ self.openSettings(tab: .about)
167
+ }
164
168
 
165
169
  func openMenuFromShortcut() {
166
170
  if self.shouldMergeIcons {
@@ -602,7 +606,7 @@ extension StatusItemController {
602
606
  self.presentLoginAlert(title: info.title, message: info.message)
603
607
  }
604
608
 
605
- struct LoginAlertInfo: Equatable, Sendable {
609
+ struct LoginAlertInfo: Equatable {
606
610
  let title: String
607
611
  let message: String
608
612
  }
@@ -1,8 +1,9 @@
1
1
  import AppKit
2
- import RunicCore
3
2
  import QuartzCore
3
+ import RunicCore
4
4
 
5
5
  // MARK: - Animation & Performance Optimization
6
+
6
7
  //
7
8
  // **Zero Token Leakage Policy**:
8
9
  // - Use cookies/local storage from providers FIRST
@@ -433,14 +434,14 @@ extension StatusItemController {
433
434
  let isMerged = mergeIcons ?? self.shouldMergeIcons
434
435
  let isVisible = isMerged ? self.isEnabled(provider) : self.isVisible(provider)
435
436
  guard isVisible else { return false }
436
-
437
+
437
438
  // Critical: Only animate while actively fetching
438
439
  guard self.store.refreshingProviders.contains(provider) else { return false }
439
-
440
+
440
441
  // Stop animation if we have data OR reached error state (stale = error/cached)
441
442
  let isStale = self.store.isStale(provider: provider)
442
443
  let hasData = self.store.snapshot(for: provider) != nil
443
-
444
+
444
445
  // Animation only runs when: no data yet AND not in error/stale state
445
446
  return !hasData && !isStale
446
447
  }
@@ -59,7 +59,9 @@ extension StatusItemController {
59
59
  let reliability = self.store.ledgerReliabilityScore(for: provider)
60
60
  let routing = self.store.ledgerRoutingRecommendation(for: provider)
61
61
  let hasActiveBlock = activeBlock?.isActive == true
62
- guard daily != nil || hasActiveBlock || !modelBreakdown.isEmpty || !projectBreakdown.isEmpty || reliability != nil || routing != nil else {
62
+ guard daily != nil || hasActiveBlock || !modelBreakdown.isEmpty || !projectBreakdown
63
+ .isEmpty || reliability != nil || routing != nil
64
+ else {
63
65
  return nil
64
66
  }
65
67
 
@@ -230,22 +232,21 @@ extension StatusItemController {
230
232
  includeAttribution: Bool = false) -> String
231
233
  {
232
234
  let trimmedProjectName = projectName?.trimmingCharacters(in: .whitespacesAndNewlines)
233
- let displayName: String
234
- if let trimmedProjectName, !trimmedProjectName.isEmpty {
235
- displayName = trimmedProjectName
235
+ let displayName: String = if let trimmedProjectName, !trimmedProjectName.isEmpty {
236
+ trimmedProjectName
236
237
  } else if let projectID, !projectID.isEmpty {
237
238
  if let budgetName = ProjectBudgetStore.getBudget(projectID: projectID)?.projectName?
238
239
  .trimmingCharacters(in: .whitespacesAndNewlines),
239
240
  !budgetName.isEmpty
240
241
  {
241
- displayName = budgetName
242
+ budgetName
242
243
  } else if let fallback = UsageLedgerProjectIdentityResolver.fallbackDisplayName(projectID: projectID) {
243
- displayName = fallback
244
+ fallback
244
245
  } else {
245
- displayName = "Unknown project"
246
+ "Unknown project"
246
247
  }
247
248
  } else {
248
- displayName = "Unknown project"
249
+ "Unknown project"
249
250
  }
250
251
  guard includeAttribution else { return displayName }
251
252
  guard let annotation = self.projectNameAnnotation(
@@ -318,10 +319,10 @@ extension StatusItemController {
318
319
  return nil
319
320
  }
320
321
 
321
- var hash: UInt64 = 0xcbf29ce484222325
322
+ var hash: UInt64 = 0xCBF2_9CE4_8422_2325
322
323
  for byte in projectID.lowercased().utf8 {
323
324
  hash ^= UInt64(byte)
324
- hash = hash &* 0x100000001b3
325
+ hash = hash &* 0x100_0000_01B3
325
326
  }
326
327
  return String(format: "%08llx", hash)
327
328
  }
@@ -336,6 +337,7 @@ extension StatusItemController {
336
337
  let submenu = NSMenu()
337
338
  submenu.delegate = self
338
339
  let chartView = UsageBreakdownChartMenuView(breakdown: breakdown, width: width)
340
+ .runicTypography()
339
341
  let hosting = MenuHostingView(rootView: chartView)
340
342
  let controller = NSHostingController(rootView: chartView)
341
343
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -357,6 +359,7 @@ extension StatusItemController {
357
359
  let submenu = NSMenu()
358
360
  submenu.delegate = self
359
361
  let chartView = CreditsHistoryChartMenuView(breakdown: breakdown, width: width)
362
+ .runicTypography()
360
363
  let hosting = MenuHostingView(rootView: chartView)
361
364
  let controller = NSHostingController(rootView: chartView)
362
365
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -383,6 +386,7 @@ extension StatusItemController {
383
386
  daily: tokenSnapshot.daily,
384
387
  totalCostUSD: tokenSnapshot.last30DaysCostUSD,
385
388
  width: width)
389
+ .runicTypography()
386
390
  let hosting = MenuHostingView(rootView: chartView)
387
391
  let controller = NSHostingController(rootView: chartView)
388
392
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -411,6 +415,7 @@ extension StatusItemController {
411
415
  dailySummaries: dailySummaries,
412
416
  hourlySummaries: hourlySummaries,
413
417
  width: width)
418
+ .runicTypography()
414
419
  let hosting = MenuHostingView(rootView: chartView)
415
420
  let controller = NSHostingController(rootView: chartView)
416
421
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -438,6 +443,7 @@ extension StatusItemController {
438
443
  let submenu = NSMenu()
439
444
  submenu.delegate = self
440
445
  let chartView = HourlyActivityChartMenuView(hourlySummaries: hourlySummaries, width: width)
446
+ .runicTypography()
441
447
  let hosting = MenuHostingView(rootView: chartView)
442
448
  let controller = NSHostingController(rootView: chartView)
443
449
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -465,6 +471,7 @@ extension StatusItemController {
465
471
  let submenu = NSMenu()
466
472
  submenu.delegate = self
467
473
  let chartView = WeeklyActivityChartMenuView(dailySummaries: dailySummaries, width: width)
474
+ .runicTypography()
468
475
  let hosting = MenuHostingView(rootView: chartView)
469
476
  let controller = NSHostingController(rootView: chartView)
470
477
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -502,6 +509,7 @@ extension StatusItemController {
502
509
  currentUsedPercent: currentUsedPercent,
503
510
  todayTokens: todayTokens,
504
511
  width: width)
512
+ .runicTypography()
505
513
  let hosting = MenuHostingView(rootView: chartView)
506
514
  let controller = NSHostingController(rootView: chartView)
507
515
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -544,6 +552,7 @@ extension StatusItemController {
544
552
  primaryPercent: primaryPercent,
545
553
  secondaryPercent: secondaryPercent,
546
554
  width: width)
555
+ .runicTypography()
547
556
  let hosting = MenuHostingView(rootView: chartView)
548
557
  let controller = NSHostingController(rootView: chartView)
549
558
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -572,6 +581,7 @@ extension StatusItemController {
572
581
  let submenu = NSMenu()
573
582
  submenu.delegate = self
574
583
  let chartView = ProjectBreakdownMenuView(breakdown: breakdown, width: width)
584
+ .runicTypography()
575
585
  let hosting = MenuHostingView(rootView: chartView)
576
586
  let controller = NSHostingController(rootView: chartView)
577
587
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -598,6 +608,7 @@ extension StatusItemController {
598
608
  if !breakdown.isEmpty {
599
609
  let width = Self.menuCardBaseWidth
600
610
  let chartView = ModelBreakdownMenuView(breakdown: breakdown, width: width)
611
+ .runicTypography()
601
612
  let hosting = MenuHostingView(rootView: chartView)
602
613
  let controller = NSHostingController(rootView: chartView)
603
614
  let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
@@ -663,7 +674,7 @@ extension StatusItemController {
663
674
 
664
675
  private func modelQuotaWindows(for provider: UsageProvider) -> [RateWindow] {
665
676
  guard let snapshot = self.store.snapshot(for: provider) else { return [] }
666
- let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap { $0 }
677
+ let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
667
678
  var seen: Set<String> = []
668
679
  var result: [RateWindow] = []
669
680
 
@@ -691,8 +702,9 @@ extension StatusItemController {
691
702
  submenu.delegate = self
692
703
 
693
704
  // MARK: Model usage (24h) — tokens, prompts, estimated cost per model
705
+
694
706
  if let modelUsage = zai.modelUsage, !modelUsage.entries.isEmpty {
695
- let headerFont = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
707
+ let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
696
708
  let header = NSMenuItem(title: "Models (24h)", action: nil, keyEquivalent: "")
697
709
  header.attributedTitle = NSAttributedString(string: "Models (24h)", attributes: [.font: headerFont])
698
710
  header.isEnabled = false
@@ -706,7 +718,7 @@ extension StatusItemController {
706
718
  title: "\(totalTokensStr) tokens · \(totalPromptsStr)\(totalCostStr)",
707
719
  action: nil, keyEquivalent: "")
708
720
  summaryItem.isEnabled = false
709
- let summaryFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
721
+ let summaryFont = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
710
722
  summaryItem.attributedTitle = NSAttributedString(
711
723
  string: summaryItem.title,
712
724
  attributes: [.font: summaryFont, .foregroundColor: NSColor.secondaryLabelColor])
@@ -732,8 +744,9 @@ extension StatusItemController {
732
744
  }
733
745
 
734
746
  // MARK: Tool usage (24h) — MCP tool call counts
747
+
735
748
  if let toolUsage = zai.toolUsage, !toolUsage.entries.isEmpty {
736
- let headerFont = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
749
+ let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
737
750
  let header = NSMenuItem(title: "MCP Tools (24h)", action: nil, keyEquivalent: "")
738
751
  header.attributedTitle = NSAttributedString(string: "MCP Tools (24h)", attributes: [.font: headerFont])
739
752
  header.isEnabled = false
@@ -743,7 +756,7 @@ extension StatusItemController {
743
756
  title: "\(toolUsage.totalCalls) total calls",
744
757
  action: nil, keyEquivalent: "")
745
758
  totalItem.isEnabled = false
746
- let totalFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
759
+ let totalFont = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
747
760
  totalItem.attributedTitle = NSAttributedString(
748
761
  string: totalItem.title,
749
762
  attributes: [.font: totalFont, .foregroundColor: NSColor.secondaryLabelColor])
@@ -764,8 +777,9 @@ extension StatusItemController {
764
777
  }
765
778
 
766
779
  // MARK: Quota MCP details (existing — from timeLimit.usageDetails)
780
+
767
781
  if let timeLimit = zai.timeLimit, !timeLimit.usageDetails.isEmpty {
768
- let headerFont = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
782
+ let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
769
783
  let header = NSMenuItem(title: "Quota Window", action: nil, keyEquivalent: "")
770
784
  header.attributedTitle = NSAttributedString(string: "Quota Window", attributes: [.font: headerFont])
771
785
  header.isEnabled = false
@@ -13,7 +13,7 @@ extension StatusItemController {
13
13
 
14
14
  let headerItem = NSMenuItem(title: "Custom Providers", action: nil, keyEquivalent: "")
15
15
  headerItem.isEnabled = false
16
- let font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
16
+ let font = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
17
17
  headerItem.attributedTitle = NSAttributedString(string: "Custom Providers", attributes: [.font: font])
18
18
  menu.addItem(headerItem)
19
19
 
@@ -28,7 +28,10 @@ extension StatusItemController {
28
28
  }
29
29
  }
30
30
 
31
- private func makeCustomProviderMenuItem(provider: CustomProviderConfig, snapshot: CustomProviderSnapshot) -> NSMenuItem {
31
+ private func makeCustomProviderMenuItem(
32
+ provider: CustomProviderConfig,
33
+ snapshot: CustomProviderSnapshot) -> NSMenuItem
34
+ {
32
35
  let title: String
33
36
  if let quota = snapshot.usageData.quota, let used = snapshot.usageData.used, quota > 0 {
34
37
  let percent = Int((used / quota) * 100)
@@ -61,7 +64,7 @@ extension StatusItemController {
61
64
  item.image = image
62
65
  }
63
66
 
64
- let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
67
+ let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
65
68
  let subtitle = NSAttributedString(
66
69
  string: error,
67
70
  attributes: [.font: font, .foregroundColor: NSColor.systemRed])
@@ -1,12 +1,12 @@
1
1
  import AppKit
2
- import RunicCore
3
2
  import Observation
3
+ import RunicCore
4
4
  import SwiftUI
5
5
 
6
6
  // MARK: - NSMenu construction
7
7
 
8
8
  extension StatusItemController {
9
- static let menuCardBaseWidth: CGFloat = 310
9
+ static let menuCardBaseWidth: CGFloat = 340
10
10
  private static let menuOpenPingDelay: Duration = PerformanceConstants.menuOpenPingDelay
11
11
 
12
12
  func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat {
@@ -148,8 +148,9 @@ extension StatusItemController {
148
148
  width: menuWidth,
149
149
  menu: menu)
150
150
  if let tabBarView {
151
- let hosting = MenuHostingView(rootView: tabBarView)
152
- let controller = NSHostingController(rootView: tabBarView)
151
+ let monoTabBar = tabBarView.runicTypography()
152
+ let hosting = MenuHostingView(rootView: monoTabBar)
153
+ let controller = NSHostingController(rootView: monoTabBar)
153
154
  let size = controller.sizeThatFits(in: CGSize(width: menuWidth, height: .greatestFiniteMagnitude))
154
155
  hosting.frame = NSRect(origin: .zero, size: NSSize(width: menuWidth, height: size.height))
155
156
  let tabItem = NSMenuItem()
@@ -172,8 +173,9 @@ extension StatusItemController {
172
173
  let overviewView = self.makeOverviewView(
173
174
  providers: enabledProviders,
174
175
  width: menuWidth)
175
- let hosting = MenuHostingView(rootView: overviewView)
176
- let controller = NSHostingController(rootView: overviewView)
176
+ let monoOverview = overviewView.runicTypography()
177
+ let hosting = MenuHostingView(rootView: monoOverview)
178
+ let controller = NSHostingController(rootView: monoOverview)
177
179
  let size = controller.sizeThatFits(in: CGSize(width: menuWidth, height: .greatestFiniteMagnitude))
178
180
  hosting.frame = NSRect(origin: .zero, size: NSSize(width: menuWidth, height: size.height))
179
181
  let overviewItem = NSMenuItem()
@@ -266,10 +268,10 @@ extension StatusItemController {
266
268
  let item = NSMenuItem(title: text, action: nil, keyEquivalent: "")
267
269
  item.isEnabled = false
268
270
  if style == .headline {
269
- let font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
271
+ let font = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
270
272
  item.attributedTitle = NSAttributedString(string: text, attributes: [.font: font])
271
273
  } else if style == .secondary {
272
- let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
274
+ let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
273
275
  item.attributedTitle = NSAttributedString(
274
276
  string: text,
275
277
  attributes: [.font: font, .foregroundColor: NSColor.secondaryLabelColor])
@@ -306,6 +308,8 @@ extension StatusItemController {
306
308
  menu.addItem(.separator())
307
309
  }
308
310
  }
311
+
312
+ self.applyRunicFont(to: menu)
309
313
  }
310
314
 
311
315
  func makeMenu(for provider: UsageProvider?) -> NSMenu {
@@ -502,7 +506,7 @@ extension StatusItemController {
502
506
 
503
507
  private static func defaultContextLabel(for provider: UsageProvider) -> String? {
504
508
  guard let k = self.defaultContextWindows[provider.rawValue] else { return nil }
505
- return k >= 1_000 ? "ctx \(k / 1_000)M" : "ctx \(k)K"
509
+ return k >= 1000 ? "ctx \(k / 1000)M" : "ctx \(k)K"
506
510
  }
507
511
 
508
512
  private func resolvedMenuProvider() -> UsageProvider? {
@@ -678,7 +682,7 @@ extension StatusItemController {
678
682
  }
679
683
 
680
684
  private func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {
681
- let ids: Set<String> = [
685
+ let ids: Set = [
682
686
  "usageBreakdownChart",
683
687
  "creditsHistoryChart",
684
688
  "costHistoryChart",
@@ -838,7 +842,7 @@ private final class ProviderSwitcherView: NSView {
838
842
  button.bezelStyle = .regularSquare
839
843
  button.isBordered = false
840
844
  button.controlSize = .small
841
- button.font = NSFont.systemFont(ofSize: 12, weight: .medium)
845
+ button.font = RunicFont.nsFont(size: 12, weight: .medium)
842
846
  button.setButtonType(.toggle)
843
847
  button.contentTintColor = self.unselectedTextColor
844
848
  button.alignment = .center
@@ -1224,7 +1228,7 @@ private final class ProviderSwitcherView: NSView {
1224
1228
  if self.stackedIcons,
1225
1229
  self.segments.indices.contains(index)
1226
1230
  {
1227
- let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
1231
+ let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
1228
1232
  let titleWidth = ceil((self.segments[index].title as NSString).size(withAttributes: [.font: font])
1229
1233
  .width)
1230
1234
  let contentPadding: CGFloat = 6 + 6
@@ -1358,7 +1362,8 @@ private final class ProviderSwitcherView: NSView {
1358
1362
 
1359
1363
  let track = NSView()
1360
1364
  track.wantsLayer = true
1361
- track.layer?.backgroundColor = NSColor.tertiaryLabelColor.withAlphaComponent(CGFloat(RunicColors.Opacity.medium)).cgColor
1365
+ track.layer?.backgroundColor = NSColor.tertiaryLabelColor
1366
+ .withAlphaComponent(CGFloat(RunicColors.Opacity.medium)).cgColor
1362
1367
  track.layer?.cornerRadius = 3
1363
1368
  track.layer?.masksToBounds = true
1364
1369
  track.translatesAutoresizingMaskIntoConstraints = false
@@ -64,8 +64,8 @@ extension StatusItemController {
64
64
  {
65
65
  let menuMode = self.settings.menuMode
66
66
  let includeSummarySections = menuMode != .glance
67
- let includeInsights = menuMode == .`operator`
68
- let includeActions = menuMode == .`operator`
67
+ let includeInsights = menuMode == .operator
68
+ let includeActions = menuMode == .operator
69
69
  let hasUsageBlock = !model.metrics.isEmpty || model.placeholder != nil
70
70
  let hasCredits = includeSummarySections && model.creditsText != nil
71
71
  let hasExtraUsage = includeSummarySections && model.providerCost != nil
@@ -282,7 +282,9 @@ extension StatusItemController {
282
282
  formatter.dateFormat = "yyyy-MM-dd"
283
283
  formatter.timeZone = TimeZone.current
284
284
  var tokensByDay: [String: Int] = [:]
285
- for d in allDaily { tokensByDay[d.dayKey, default: 0] += d.totals.totalTokens }
285
+ for d in allDaily {
286
+ tokensByDay[d.dayKey, default: 0] += d.totals.totalTokens
287
+ }
286
288
  return (0..<7).map { offset in
287
289
  guard let date = calendar.date(byAdding: .day, value: offset - 6, to: todayStart) else { return 0 }
288
290
  return tokensByDay[formatter.string(from: date)] ?? 0
@@ -306,7 +308,7 @@ extension StatusItemController {
306
308
  let updatedAt: Date? = [
307
309
  self.store.snapshot(for: provider)?.updatedAt,
308
310
  self.store.ledgerUpdatedAt[provider],
309
- ].compactMap { $0 }.max()
311
+ ].compactMap(\.self).max()
310
312
 
311
313
  if let updatedAt {
312
314
  let timestampView = self.menuCardContent(width: width, sidebar: sidebar, showIcons: true) {
@@ -36,16 +36,26 @@ extension StatusItemController {
36
36
  }
37
37
 
38
38
  final class MenuHostingView<Content: View>: NSHostingView<Content> {
39
- override var allowsVibrancy: Bool { true }
40
- override var isOpaque: Bool { false }
39
+ override var allowsVibrancy: Bool {
40
+ true
41
+ }
42
+
43
+ override var isOpaque: Bool {
44
+ false
45
+ }
41
46
  }
42
47
 
43
48
  @MainActor
44
49
  final class MenuCardItemHostingView<Content: View>: NSHostingView<Content>, MenuCardHighlighting,
45
50
  MenuCardMeasuring {
46
51
  private let highlightState: MenuCardHighlightState
47
- override var allowsVibrancy: Bool { true }
48
- override var isOpaque: Bool { false }
52
+ override var allowsVibrancy: Bool {
53
+ true
54
+ }
55
+
56
+ override var isOpaque: Bool {
57
+ false
58
+ }
49
59
 
50
60
  override var intrinsicContentSize: NSSize {
51
61
  let size = super.intrinsicContentSize
@@ -98,6 +108,7 @@ extension StatusItemController {
98
108
 
99
109
  var body: some View {
100
110
  self.content
111
+ .runicTypography()
101
112
  .environment(\.menuItemHighlighted, self.highlightState.isHighlighted)
102
113
  .foregroundStyle(MenuHighlightStyle.primary(self.highlightState.isHighlighted))
103
114
  .background(alignment: .topLeading) {
@@ -111,7 +122,7 @@ extension StatusItemController {
111
122
  .overlay(alignment: .topTrailing) {
112
123
  if self.showsSubmenuIndicator {
113
124
  Image(systemName: "chevron.right")
114
- .font(.caption2.weight(.semibold))
125
+ .font(RunicFont.caption2.weight(.semibold))
115
126
  .foregroundStyle(MenuHighlightStyle.secondary(self.highlightState.isHighlighted))
116
127
  .padding(.top, RunicSpacing.xs)
117
128
  .padding(.trailing, RunicSpacing.xs)
@@ -142,7 +153,7 @@ extension StatusItemController {
142
153
  self.iconView.imageScaling = .scaleNone
143
154
  self.iconView.translatesAutoresizingMaskIntoConstraints = false
144
155
 
145
- self.titleField.font = NSFont.menuFont(ofSize: NSFont.systemFontSize)
156
+ self.titleField.font = RunicFont.nsFont(size: NSFont.systemFontSize)
146
157
  self.titleField.lineBreakMode = .byTruncatingTail
147
158
  self.titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
148
159
 
@@ -217,11 +228,11 @@ extension StatusItemController {
217
228
  // MARK: - Sidebar content builder
218
229
 
219
230
  @ViewBuilder
220
- func menuCardContent<Content: View>(
231
+ func menuCardContent(
221
232
  width: CGFloat,
222
233
  sidebar: MenuCardSidebarConfig?,
223
234
  showIcons: Bool,
224
- @ViewBuilder content: @escaping (CGFloat) -> Content) -> some View
235
+ @ViewBuilder content: @escaping (CGFloat) -> some View) -> some View
225
236
  {
226
237
  if let sidebar {
227
238
  ProviderSidebarMenuCardView(
@@ -325,8 +336,8 @@ extension StatusItemController {
325
336
  let image = NSImage(
326
337
  systemSymbolName: MenuDescriptor.MenuActionSystemImage.refresh.rawValue,
327
338
  accessibilityDescription: nil) ?? NSImage(
328
- systemSymbolName: "arrow.triangle.2.circlepath",
329
- accessibilityDescription: nil)
339
+ systemSymbolName: "arrow.triangle.2.circlepath",
340
+ accessibilityDescription: nil)
330
341
  image?.isTemplate = true
331
342
  image?.size = NSSize(width: 16, height: 16)
332
343
  let view = MenuActionButtonView(title: title, image: image) { [weak self] in
@@ -363,14 +374,14 @@ extension StatusItemController {
363
374
  container.alphaValue = isEnabled ? 1.0 : 0.7
364
375
 
365
376
  let titleField = NSTextField(labelWithString: title)
366
- titleField.font = NSFont.menuFont(ofSize: NSFont.systemFontSize)
377
+ titleField.font = RunicFont.nsFont(size: NSFont.systemFontSize)
367
378
  titleField.textColor = NSColor.labelColor
368
379
  titleField.lineBreakMode = .byTruncatingTail
369
380
  titleField.maximumNumberOfLines = 1
370
381
  titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
371
382
 
372
383
  let subtitleField = NSTextField(labelWithString: subtitle)
373
- subtitleField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)
384
+ subtitleField.font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
374
385
  subtitleField.textColor = NSColor.secondaryLabelColor
375
386
  subtitleField.lineBreakMode = .byTruncatingTail
376
387
  subtitleField.maximumNumberOfLines = 1
@@ -392,4 +403,21 @@ extension StatusItemController {
392
403
 
393
404
  return container
394
405
  }
406
+
407
+ // MARK: - Fira Code menu font post-processor
408
+
409
+ /// Recursively apply Fira Code to every NSMenuItem that uses a plain title (no custom view).
410
+ func applyRunicFont(to menu: NSMenu) {
411
+ for item in menu.items {
412
+ guard !item.isSeparatorItem, item.view == nil, !item.title.isEmpty else { continue }
413
+ if item.attributedTitle == nil {
414
+ item.attributedTitle = NSAttributedString(
415
+ string: item.title,
416
+ attributes: [.font: RunicFont.nsFont(size: NSFont.systemFontSize)])
417
+ }
418
+ if let submenu = item.submenu {
419
+ self.applyRunicFont(to: submenu)
420
+ }
421
+ }
422
+ }
395
423
  }