@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
@@ -19,13 +19,15 @@ struct AlertsPane: View {
19
19
  case discord
20
20
  case generic
21
21
 
22
- var id: String { self.rawValue }
22
+ var id: String {
23
+ self.rawValue
24
+ }
23
25
 
24
26
  var label: String {
25
27
  switch self {
26
- case .slack: return "Slack"
27
- case .discord: return "Discord"
28
- case .generic: return "Generic"
28
+ case .slack: "Slack"
29
+ case .discord: "Discord"
30
+ case .generic: "Generic"
29
31
  }
30
32
  }
31
33
  }
@@ -34,14 +36,14 @@ struct AlertsPane: View {
34
36
  PreferencesPane {
35
37
  SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
36
38
  Text("Alert Rules")
37
- .font(.caption)
39
+ .font(RunicFont.caption)
38
40
  .foregroundStyle(.secondary)
39
41
  .textCase(.uppercase)
40
42
 
41
43
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
42
44
  HStack {
43
45
  Text("\(self.alertsData.rules.count) rules configured")
44
- .font(.footnote)
46
+ .font(RunicFont.footnote)
45
47
  .foregroundStyle(.secondary)
46
48
 
47
49
  Spacer()
@@ -57,7 +59,7 @@ struct AlertsPane: View {
57
59
 
58
60
  if self.alertsData.rules.isEmpty {
59
61
  Text("No alert rules configured. Add a rule to get started.")
60
- .font(.footnote)
62
+ .font(RunicFont.footnote)
61
63
  .foregroundStyle(.tertiary)
62
64
  .padding(.vertical, RunicSpacing.md)
63
65
  .frame(maxWidth: .infinity)
@@ -76,24 +78,24 @@ struct AlertsPane: View {
76
78
 
77
79
  SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
78
80
  Text("Webhook Configuration")
79
- .font(.caption)
81
+ .font(RunicFont.caption)
80
82
  .foregroundStyle(.secondary)
81
83
  .textCase(.uppercase)
82
84
 
83
85
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
84
86
  VStack(alignment: .leading, spacing: 4) {
85
87
  Text("Default webhook URL")
86
- .font(.body)
88
+ .font(RunicFont.body)
87
89
  TextField("https://hooks.slack.com/services/...", text: self.$defaultWebhookURL)
88
90
  .textFieldStyle(.roundedBorder)
89
91
  Text("Used for all alerts unless overridden in rule settings.")
90
- .font(.footnote)
92
+ .font(RunicFont.footnote)
91
93
  .foregroundStyle(.tertiary)
92
94
  }
93
95
 
94
96
  VStack(alignment: .leading, spacing: 4) {
95
97
  Text("Webhook format")
96
- .font(.body)
98
+ .font(RunicFont.body)
97
99
  Picker("", selection: self.$webhookFormat) {
98
100
  ForEach(WebhookFormat.allCases) { format in
99
101
  Text(format.label).tag(format)
@@ -112,7 +114,7 @@ struct AlertsPane: View {
112
114
 
113
115
  if let result = self.showingTestResult {
114
116
  Text(result)
115
- .font(.footnote)
117
+ .font(RunicFont.footnote)
116
118
  .foregroundStyle(result.contains("Success") ? .green : .red)
117
119
  }
118
120
  }
@@ -123,14 +125,14 @@ struct AlertsPane: View {
123
125
 
124
126
  SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
125
127
  Text("Alert History")
126
- .font(.caption)
128
+ .font(RunicFont.caption)
127
129
  .foregroundStyle(.secondary)
128
130
  .textCase(.uppercase)
129
131
 
130
132
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
131
133
  HStack {
132
134
  Text("Last 20 triggered alerts")
133
- .font(.footnote)
135
+ .font(RunicFont.footnote)
134
136
  .foregroundStyle(.secondary)
135
137
 
136
138
  Spacer()
@@ -147,7 +149,7 @@ struct AlertsPane: View {
147
149
 
148
150
  if recentHistory.isEmpty {
149
151
  Text("No alerts triggered yet.")
150
- .font(.footnote)
152
+ .font(RunicFont.footnote)
151
153
  .foregroundStyle(.tertiary)
152
154
  .padding(.vertical, RunicSpacing.md)
153
155
  .frame(maxWidth: .infinity)
@@ -174,8 +176,7 @@ struct AlertsPane: View {
174
176
  } catch {
175
177
  print("Failed to add rule: \(error)")
176
178
  }
177
- }
178
- )
179
+ })
179
180
  }
180
181
  .sheet(item: self.$editingRule) { rule in
181
182
  RuleEditorSheet(
@@ -187,8 +188,7 @@ struct AlertsPane: View {
187
188
  } catch {
188
189
  print("Failed to update rule: \(error)")
189
190
  }
190
- }
191
- )
191
+ })
192
192
  }
193
193
  }
194
194
 
@@ -201,21 +201,20 @@ struct AlertsPane: View {
201
201
  updatedRule.enabled = enabled
202
202
  try? AlertRuleStore.updateRule(updatedRule)
203
203
  self.alertsData = AlertRuleStore.load()
204
- }
205
- ))
206
- .toggleStyle(.switch)
207
- .controlSize(.small)
204
+ }))
205
+ .toggleStyle(.switch)
206
+ .controlSize(.small)
208
207
 
209
208
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
210
209
  HStack(spacing: RunicSpacing.xs) {
211
210
  Text(self.alertTypeLabel(rule.type))
212
- .font(.body)
211
+ .font(RunicFont.body)
213
212
 
214
213
  self.severityBadge(rule.severity)
215
214
  }
216
215
 
217
216
  Text("Threshold: \(Int(rule.threshold))%")
218
- .font(.footnote)
217
+ .font(RunicFont.footnote)
219
218
  .foregroundStyle(.secondary)
220
219
  }
221
220
 
@@ -251,11 +250,11 @@ struct AlertsPane: View {
251
250
 
252
251
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
253
252
  Text(entry.message)
254
- .font(.footnote)
253
+ .font(RunicFont.footnote)
255
254
  .lineLimit(2)
256
255
 
257
256
  Text(self.relativeTime(entry.triggeredAt))
258
- .font(.caption2)
257
+ .font(RunicFont.caption2)
259
258
  .foregroundStyle(.tertiary)
260
259
  }
261
260
 
@@ -277,16 +276,14 @@ struct AlertsPane: View {
277
276
  }
278
277
 
279
278
  private func severityBadge(_ severity: AlertRuleStore.AlertSeverity) -> some View {
280
- let color: Color = {
281
- switch severity {
282
- case .info: return .blue
283
- case .warning: return .orange
284
- case .critical: return .red
285
- }
286
- }()
279
+ let color: Color = switch severity {
280
+ case .info: .blue
281
+ case .warning: .orange
282
+ case .critical: .red
283
+ }
287
284
 
288
285
  return Text(severity.rawValue.uppercased())
289
- .font(.caption2.weight(.semibold))
286
+ .font(RunicFont.caption2.weight(.semibold))
290
287
  .padding(.horizontal, RunicSpacing.xs)
291
288
  .padding(.vertical, RunicSpacing.xxs)
292
289
  .background(color.opacity(0.2))
@@ -296,10 +293,10 @@ struct AlertsPane: View {
296
293
 
297
294
  private func alertTypeLabel(_ type: AlertRuleStore.AlertType) -> String {
298
295
  switch type {
299
- case .projectBudget: return "Project Budget"
300
- case .usageVelocity: return "Usage Velocity"
301
- case .costAnomaly: return "Cost Anomaly"
302
- case .quotaThreshold: return "Quota Threshold"
296
+ case .projectBudget: "Project Budget"
297
+ case .usageVelocity: "Usage Velocity"
298
+ case .costAnomaly: "Cost Anomaly"
299
+ case .quotaThreshold: "Quota Threshold"
303
300
  }
304
301
  }
305
302
 
@@ -365,7 +362,7 @@ private struct RuleEditorSheet: View {
365
362
  var body: some View {
366
363
  VStack(spacing: RunicSpacing.lg) {
367
364
  Text(self.rule == nil ? "Add Alert Rule" : "Edit Alert Rule")
368
- .font(.headline)
365
+ .font(RunicFont.headline)
369
366
 
370
367
  Form {
371
368
  Picker("Alert Type", selection: self.$alertType) {
@@ -408,8 +405,7 @@ private struct RuleEditorSheet: View {
408
405
  severity: self.severity,
409
406
  notifyWebhook: self.notifyWebhook,
410
407
  webhookURL: self.webhookURL.isEmpty ? nil : self.webhookURL,
411
- createdAt: self.rule?.createdAt ?? Date()
412
- )
408
+ createdAt: self.rule?.createdAt ?? Date())
413
409
  self.onSave(newRule)
414
410
  self.dismiss()
415
411
  }
@@ -45,7 +45,7 @@ struct AnalyticsPane: View {
45
45
  binding: self.$settings.usageBarsShowUsed)
46
46
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
47
47
  Text("Usage metrics")
48
- .font(.body)
48
+ .font(RunicFont.body)
49
49
  Picker("", selection: self.$settings.usageMetricDisplayMode) {
50
50
  ForEach(UsageMetricDisplayMode.allCases) { mode in
51
51
  Text(mode.label).tag(mode)
@@ -53,13 +53,13 @@ struct AnalyticsPane: View {
53
53
  }
54
54
  .pickerStyle(.segmented)
55
55
  Text("Pick bars, percent, or both in the menu card.")
56
- .font(.footnote)
56
+ .font(RunicFont.footnote)
57
57
  .foregroundStyle(.tertiary)
58
58
  .fixedSize(horizontal: false, vertical: true)
59
59
  }
60
60
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
61
61
  Text("Menu mode")
62
- .font(.body)
62
+ .font(RunicFont.body)
63
63
  Picker("", selection: self.$settings.menuMode) {
64
64
  ForEach(MenuMode.allCases) { mode in
65
65
  Text(mode.label).tag(mode)
@@ -67,7 +67,7 @@ struct AnalyticsPane: View {
67
67
  }
68
68
  .pickerStyle(.segmented)
69
69
  Text("Glance: usage only. Analyst: usage + credits/cost. Operator: full insights and actions.")
70
- .font(.footnote)
70
+ .font(RunicFont.footnote)
71
71
  .foregroundStyle(.tertiary)
72
72
  .fixedSize(horizontal: false, vertical: true)
73
73
  }
@@ -81,7 +81,7 @@ struct AnalyticsPane: View {
81
81
  binding: self.$settings.mergeIcons)
82
82
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
83
83
  Text("Provider switcher layout")
84
- .font(.body)
84
+ .font(RunicFont.body)
85
85
  Picker("", selection: self.$settings.providerSwitcherLayout) {
86
86
  ForEach(ProviderSwitcherLayout.allCases) { layout in
87
87
  Text(layout.label).tag(layout)
@@ -91,13 +91,13 @@ struct AnalyticsPane: View {
91
91
  .disabled(!self.settings.mergeIcons)
92
92
  .opacity(self.settings.mergeIcons ? 1 : 0.5)
93
93
  Text("Choose whether providers appear on top or in a left sidebar.")
94
- .font(.footnote)
94
+ .font(RunicFont.footnote)
95
95
  .foregroundStyle(.tertiary)
96
96
  .fixedSize(horizontal: false, vertical: true)
97
97
  }
98
98
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
99
99
  Text("Switcher icon size")
100
- .font(.body)
100
+ .font(RunicFont.body)
101
101
  Picker("", selection: self.$settings.providerSwitcherIconSize) {
102
102
  ForEach(ProviderSwitcherIconSize.allCases) { size in
103
103
  Text(size.label).tag(size)
@@ -107,7 +107,7 @@ struct AnalyticsPane: View {
107
107
  .disabled(!self.settings.mergeIcons)
108
108
  .opacity(self.settings.mergeIcons ? 1 : 0.5)
109
109
  Text("Small or medium icons for the provider switcher.")
110
- .font(.footnote)
110
+ .font(RunicFont.footnote)
111
111
  .foregroundStyle(.tertiary)
112
112
  .fixedSize(horizontal: false, vertical: true)
113
113
  }
@@ -164,7 +164,7 @@ struct AnalyticsPane: View {
164
164
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
165
165
  HStack {
166
166
  Text("\(self.alertsData.rules.count) rules configured")
167
- .font(.footnote)
167
+ .font(RunicFont.footnote)
168
168
  .foregroundStyle(.secondary)
169
169
 
170
170
  Spacer()
@@ -180,7 +180,7 @@ struct AnalyticsPane: View {
180
180
 
181
181
  if self.alertsData.rules.isEmpty {
182
182
  Text("No alert rules configured. Add a rule to get started.")
183
- .font(.footnote)
183
+ .font(RunicFont.footnote)
184
184
  .foregroundStyle(.tertiary)
185
185
  .padding(.vertical, RunicSpacing.md)
186
186
  .frame(maxWidth: .infinity)
@@ -207,7 +207,7 @@ struct AnalyticsPane: View {
207
207
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
208
208
  HStack {
209
209
  Text("\(self.budgets.count) budgets configured")
210
- .font(.footnote)
210
+ .font(RunicFont.footnote)
211
211
  .foregroundStyle(.secondary)
212
212
 
213
213
  Spacer()
@@ -223,7 +223,7 @@ struct AnalyticsPane: View {
223
223
 
224
224
  if self.budgets.isEmpty {
225
225
  Text("No budgets configured. Add a budget to track project spending.")
226
- .font(.footnote)
226
+ .font(RunicFont.footnote)
227
227
  .foregroundStyle(.tertiary)
228
228
  .padding(.vertical, RunicSpacing.md)
229
229
  .frame(maxWidth: .infinity)
@@ -239,10 +239,10 @@ struct AnalyticsPane: View {
239
239
  if let error = self.budgetErrorMessage {
240
240
  HStack(alignment: .center, spacing: RunicSpacing.xs) {
241
241
  Image(systemName: "exclamationmark.triangle.fill")
242
- .font(.caption)
242
+ .font(RunicFont.caption)
243
243
  .foregroundStyle(.red)
244
244
  Text(error)
245
- .font(.caption)
245
+ .font(RunicFont.caption)
246
246
  .foregroundStyle(.red)
247
247
  }
248
248
  .padding(RunicSpacing.xs)
@@ -311,21 +311,20 @@ struct AnalyticsPane: View {
311
311
  updatedRule.enabled = enabled
312
312
  try? AlertRuleStore.updateRule(updatedRule)
313
313
  self.alertsData = AlertRuleStore.load()
314
- }
315
- ))
316
- .toggleStyle(.switch)
317
- .controlSize(.small)
314
+ }))
315
+ .toggleStyle(.switch)
316
+ .controlSize(.small)
318
317
 
319
318
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
320
319
  HStack(spacing: RunicSpacing.xs) {
321
320
  Text(self.alertTypeLabel(rule.type))
322
- .font(.body)
321
+ .font(RunicFont.body)
323
322
 
324
323
  self.severityBadge(rule.severity)
325
324
  }
326
325
 
327
326
  Text("Threshold: \(Int(rule.threshold))%")
328
- .font(.footnote)
327
+ .font(RunicFont.footnote)
329
328
  .foregroundStyle(.secondary)
330
329
  }
331
330
 
@@ -361,16 +360,16 @@ struct AnalyticsPane: View {
361
360
  HStack(spacing: RunicSpacing.sm) {
362
361
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
363
362
  Text(budget.projectName ?? budget.projectID)
364
- .font(.body)
363
+ .font(RunicFont.body)
365
364
  .lineLimit(1)
366
365
  .truncationMode(.tail)
367
366
 
368
367
  HStack(spacing: RunicSpacing.xs) {
369
368
  Text(UsageFormatter.usdString(budget.monthlyLimit))
370
- .font(.footnote)
369
+ .font(RunicFont.footnote)
371
370
  .foregroundStyle(.secondary)
372
371
  Text("Alert at \(String(format: "%.0f%%", budget.alertThreshold * 100))")
373
- .font(.footnote)
372
+ .font(RunicFont.footnote)
374
373
  .foregroundStyle(.secondary)
375
374
  }
376
375
  }
@@ -378,14 +377,14 @@ struct AnalyticsPane: View {
378
377
  Spacer()
379
378
 
380
379
  Text(budget.enabled ? "Enabled" : "Disabled")
381
- .font(.caption)
380
+ .font(RunicFont.caption)
382
381
  .foregroundStyle(budget.enabled ? .green : .secondary)
383
382
 
384
383
  Button {
385
384
  self.deleteBudget(budget)
386
385
  } label: {
387
386
  Image(systemName: "trash")
388
- .font(.caption)
387
+ .font(RunicFont.caption)
389
388
  .foregroundStyle(.red)
390
389
  }
391
390
  .buttonStyle(.plain)
@@ -398,16 +397,14 @@ struct AnalyticsPane: View {
398
397
  // MARK: - Helpers
399
398
 
400
399
  private func severityBadge(_ severity: AlertRuleStore.AlertSeverity) -> some View {
401
- let color: Color = {
402
- switch severity {
403
- case .info: return .blue
404
- case .warning: return .orange
405
- case .critical: return .red
406
- }
407
- }()
400
+ let color: Color = switch severity {
401
+ case .info: .blue
402
+ case .warning: .orange
403
+ case .critical: .red
404
+ }
408
405
 
409
406
  return Text(severity.rawValue.uppercased())
410
- .font(.caption2.weight(.semibold))
407
+ .font(RunicFont.caption2.weight(.semibold))
411
408
  .padding(.horizontal, RunicSpacing.xs)
412
409
  .padding(.vertical, RunicSpacing.xxs)
413
410
  .background(color.opacity(0.2))
@@ -417,10 +414,10 @@ struct AnalyticsPane: View {
417
414
 
418
415
  private func alertTypeLabel(_ type: AlertRuleStore.AlertType) -> String {
419
416
  switch type {
420
- case .projectBudget: return "Project Budget"
421
- case .usageVelocity: return "Usage Velocity"
422
- case .costAnomaly: return "Cost Anomaly"
423
- case .quotaThreshold: return "Quota Threshold"
417
+ case .projectBudget: "Project Budget"
418
+ case .usageVelocity: "Usage Velocity"
419
+ case .costAnomaly: "Cost Anomaly"
420
+ case .quotaThreshold: "Quota Threshold"
424
421
  }
425
422
  }
426
423
 
@@ -493,11 +490,11 @@ private struct AnalyticsSectionDisclosureStyle: DisclosureGroupStyle {
493
490
  } label: {
494
491
  HStack(spacing: RunicSpacing.xs) {
495
492
  Image(systemName: configuration.isExpanded ? "chevron.down" : "chevron.right")
496
- .font(.caption.weight(.semibold))
493
+ .font(RunicFont.caption.weight(.semibold))
497
494
  .foregroundStyle(.secondary)
498
495
  .frame(width: 12)
499
496
  configuration.label
500
- .font(.caption)
497
+ .font(RunicFont.caption)
501
498
  .foregroundStyle(.secondary)
502
499
  .textCase(.uppercase)
503
500
  Spacer()
@@ -542,7 +539,7 @@ private struct AnalyticsRuleEditorSheet: View {
542
539
  var body: some View {
543
540
  VStack(spacing: RunicSpacing.lg) {
544
541
  Text(self.rule == nil ? "Add Alert Rule" : "Edit Alert Rule")
545
- .font(.headline)
542
+ .font(RunicFont.headline)
546
543
 
547
544
  Form {
548
545
  Picker("Alert Type", selection: self.$alertType) {
@@ -586,8 +583,7 @@ private struct AnalyticsRuleEditorSheet: View {
586
583
  notifyWebhook: self.notifyWebhook,
587
584
  webhookURL: self.webhookURL.isEmpty ? nil : self.webhookURL,
588
585
  enabled: self.rule?.enabled ?? true,
589
- createdAt: self.rule?.createdAt ?? Date()
590
- )
586
+ createdAt: self.rule?.createdAt ?? Date())
591
587
  self.onSave(newRule)
592
588
  self.dismiss()
593
589
  }
@@ -613,7 +609,7 @@ private struct AnalyticsAddBudgetSheet: View {
613
609
  var body: some View {
614
610
  VStack(alignment: .leading, spacing: RunicSpacing.md) {
615
611
  Text("Add Budget")
616
- .font(.title2)
612
+ .font(RunicFont.title2)
617
613
  .fontWeight(.semibold)
618
614
 
619
615
  Form {
@@ -16,17 +16,17 @@ struct PreferencesBudgetsPane: View {
16
16
  VStack(alignment: .leading, spacing: RunicSpacing.md) {
17
17
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
18
18
  Text("Project Budgets")
19
- .font(.title2)
19
+ .font(RunicFont.title2)
20
20
  .fontWeight(.semibold)
21
21
  Text("Set monthly spending limits and alerts for projects.")
22
- .font(.subheadline)
22
+ .font(RunicFont.subheadline)
23
23
  .foregroundStyle(.secondary)
24
24
  }
25
25
 
26
26
  if self.budgets.isEmpty {
27
27
  VStack(spacing: RunicSpacing.md) {
28
28
  Text("No budgets configured")
29
- .font(.body)
29
+ .font(RunicFont.body)
30
30
  .foregroundStyle(.secondary)
31
31
  .padding(.vertical, RunicSpacing.lg)
32
32
 
@@ -44,19 +44,19 @@ struct PreferencesBudgetsPane: View {
44
44
  // Header
45
45
  HStack(alignment: .center, spacing: RunicSpacing.sm) {
46
46
  Text("Project")
47
- .font(.caption)
47
+ .font(RunicFont.caption)
48
48
  .foregroundStyle(.secondary)
49
49
  .frame(width: 150, alignment: .leading)
50
50
  Text("Monthly Limit")
51
- .font(.caption)
51
+ .font(RunicFont.caption)
52
52
  .foregroundStyle(.secondary)
53
53
  .frame(width: 120, alignment: .leading)
54
54
  Text("Alert at")
55
- .font(.caption)
55
+ .font(RunicFont.caption)
56
56
  .foregroundStyle(.secondary)
57
57
  .frame(width: 80, alignment: .leading)
58
58
  Text("Status")
59
- .font(.caption)
59
+ .font(RunicFont.caption)
60
60
  .foregroundStyle(.secondary)
61
61
  .frame(width: 80, alignment: .leading)
62
62
  Spacer()
@@ -107,11 +107,11 @@ struct PreferencesBudgetsPane: View {
107
107
  if let error = self.errorMessage {
108
108
  HStack(alignment: .center, spacing: RunicSpacing.xs) {
109
109
  Image(systemName: "exclamationmark.triangle.fill")
110
- .font(.caption)
110
+ .font(RunicFont.caption)
111
111
  .foregroundStyle(.red)
112
112
  .alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
113
113
  Text(error)
114
- .font(.caption)
114
+ .font(RunicFont.caption)
115
115
  .foregroundStyle(.red)
116
116
  }
117
117
  .padding(RunicSpacing.xs)
@@ -262,20 +262,20 @@ private struct BudgetRow: View {
262
262
  .controlSize(.small)
263
263
  }
264
264
  } else {
265
- Text(budget.projectName ?? budget.projectID)
266
- .font(.body)
265
+ Text(self.budget.projectName ?? self.budget.projectID)
266
+ .font(RunicFont.body)
267
267
  .frame(width: 150, alignment: .leading)
268
268
  .lineLimit(1)
269
269
  .truncationMode(.tail)
270
- Text(UsageFormatter.usdString(budget.monthlyLimit))
271
- .font(.body)
270
+ Text(UsageFormatter.usdString(self.budget.monthlyLimit))
271
+ .font(RunicFont.body)
272
272
  .frame(width: 120, alignment: .leading)
273
- Text(String(format: "%.0f%%", budget.alertThreshold * 100))
274
- .font(.body)
273
+ Text(String(format: "%.0f%%", self.budget.alertThreshold * 100))
274
+ .font(RunicFont.body)
275
275
  .frame(width: 80, alignment: .leading)
276
- Text(budget.enabled ? "Enabled" : "Disabled")
277
- .font(.caption)
278
- .foregroundStyle(budget.enabled ? .green : .secondary)
276
+ Text(self.budget.enabled ? "Enabled" : "Disabled")
277
+ .font(RunicFont.caption)
278
+ .foregroundStyle(self.budget.enabled ? .green : .secondary)
279
279
  .frame(width: 80, alignment: .leading)
280
280
  .alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
281
281
  Spacer()
@@ -284,14 +284,14 @@ private struct BudgetRow: View {
284
284
  self.onEdit()
285
285
  } label: {
286
286
  Image(systemName: "pencil")
287
- .font(.caption)
287
+ .font(RunicFont.caption)
288
288
  }
289
289
  .buttonStyle(.plain)
290
290
  Button {
291
291
  self.onDelete()
292
292
  } label: {
293
293
  Image(systemName: "trash")
294
- .font(.caption)
294
+ .font(RunicFont.caption)
295
295
  .foregroundStyle(.red)
296
296
  }
297
297
  .buttonStyle(.plain)
@@ -316,7 +316,7 @@ private struct AddBudgetSheet: View {
316
316
  var body: some View {
317
317
  VStack(alignment: .leading, spacing: RunicSpacing.md) {
318
318
  Text("Add Budget")
319
- .font(.title2)
319
+ .font(RunicFont.title2)
320
320
  .fontWeight(.semibold)
321
321
 
322
322
  Form {
@@ -71,7 +71,7 @@ struct PreferenceToggleRow: View {
71
71
  HStack(alignment: .center, spacing: RunicSpacing.xs) {
72
72
  Toggle(isOn: self.$binding) {
73
73
  Text(self.title)
74
- .font(.body)
74
+ .font(RunicFont.body)
75
75
  }
76
76
  .toggleStyle(.checkbox)
77
77
  .alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
@@ -79,7 +79,7 @@ struct PreferenceToggleRow: View {
79
79
 
80
80
  if let subtitle, !subtitle.isEmpty {
81
81
  Text(subtitle)
82
- .font(.footnote)
82
+ .font(RunicFont.footnote)
83
83
  .foregroundStyle(.tertiary)
84
84
  .fixedSize(horizontal: false, vertical: true)
85
85
  }
@@ -100,7 +100,7 @@ struct PreferenceStepperRow: View {
100
100
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
101
101
  HStack(spacing: RunicSpacing.sm) {
102
102
  Text(self.title)
103
- .font(.body)
103
+ .font(RunicFont.body)
104
104
  Spacer()
105
105
  PreferenceStepperControl(
106
106
  valueLabel: self.valueLabel(self.value),
@@ -112,7 +112,7 @@ struct PreferenceStepperRow: View {
112
112
 
113
113
  if let subtitle, !subtitle.isEmpty {
114
114
  Text(subtitle)
115
- .font(.footnote)
115
+ .font(RunicFont.footnote)
116
116
  .foregroundStyle(.tertiary)
117
117
  .fixedSize(horizontal: false, vertical: true)
118
118
  }
@@ -146,7 +146,7 @@ private struct PreferenceStepperControl: View {
146
146
  .disabled(!self.canDecrement)
147
147
 
148
148
  Text(self.valueLabel)
149
- .font(.footnote.weight(.semibold))
149
+ .font(RunicFont.footnote.weight(.semibold))
150
150
  .foregroundStyle(.secondary)
151
151
  .padding(.horizontal, RunicSpacing.sm)
152
152
  .padding(.vertical, RunicSpacing.xxs)
@@ -187,11 +187,11 @@ struct SettingsSection<Content: View>: View {
187
187
  VStack(alignment: .leading, spacing: PreferencesLayoutMetrics.sectionHeaderSpacing) {
188
188
  if let title, !title.isEmpty {
189
189
  Text(title)
190
- .font(.subheadline.weight(.semibold))
190
+ .font(RunicFont.subheadline.weight(.semibold))
191
191
  }
192
192
  if let caption {
193
193
  Text(caption)
194
- .font(.footnote)
194
+ .font(RunicFont.footnote)
195
195
  .foregroundStyle(.tertiary)
196
196
  .fixedSize(horizontal: false, vertical: true)
197
197
  }