@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
@@ -17,7 +17,7 @@ struct CustomProvidersPane: View {
17
17
  // Header with add button
18
18
  HStack {
19
19
  Text("Custom API Providers")
20
- .font(.headline)
20
+ .font(RunicFont.headline)
21
21
 
22
22
  Spacer()
23
23
 
@@ -37,11 +37,11 @@ struct CustomProvidersPane: View {
37
37
  .foregroundStyle(.secondary)
38
38
 
39
39
  Text("No custom providers configured")
40
- .font(.headline)
40
+ .font(RunicFont.headline)
41
41
  .foregroundStyle(.secondary)
42
42
 
43
43
  Text("Add a custom API provider to track usage from APIs not natively supported by Runic.")
44
- .font(.caption)
44
+ .font(RunicFont.caption)
45
45
  .foregroundStyle(.tertiary)
46
46
  .multilineTextAlignment(.center)
47
47
  .frame(maxWidth: 300)
@@ -56,9 +56,8 @@ struct CustomProvidersPane: View {
56
56
  snapshot: self.store.customProviderSnapshots[provider.id],
57
57
  onToggle: { enabled in self.toggleProvider(provider, enabled: enabled) },
58
58
  onEdit: { self.editingProvider = provider },
59
- onDelete: { self.confirmDelete = provider }
60
- )
61
- .padding(.horizontal, PreferencesLayoutMetrics.paneHorizontal)
59
+ onDelete: { self.confirmDelete = provider })
60
+ .padding(.horizontal, PreferencesLayoutMetrics.paneHorizontal)
62
61
 
63
62
  if provider.id != self.providers.last?.id {
64
63
  Divider()
@@ -79,8 +78,7 @@ struct CustomProvidersPane: View {
79
78
  self.addProvider(provider)
80
79
  self.showingAddProvider = false
81
80
  },
82
- onCancel: { self.showingAddProvider = false }
83
- )
81
+ onCancel: { self.showingAddProvider = false })
84
82
  }
85
83
  .sheet(item: self.$editingProvider) { provider in
86
84
  CustomProviderEditorView(
@@ -89,13 +87,12 @@ struct CustomProvidersPane: View {
89
87
  self.updateProvider(updated)
90
88
  self.editingProvider = nil
91
89
  },
92
- onCancel: { self.editingProvider = nil }
93
- )
90
+ onCancel: { self.editingProvider = nil })
94
91
  }
95
92
  .alert("Delete Provider", isPresented: Binding(
96
93
  get: { self.confirmDelete != nil },
97
- set: { if !$0 { self.confirmDelete = nil } }
98
- )) {
94
+ set: { if !$0 { self.confirmDelete = nil } }))
95
+ {
99
96
  Button("Cancel", role: .cancel) { self.confirmDelete = nil }
100
97
  Button("Delete", role: .destructive) {
101
98
  if let provider = self.confirmDelete {
@@ -165,40 +162,40 @@ private struct CustomProviderRow: View {
165
162
 
166
163
  var body: some View {
167
164
  HStack(spacing: RunicSpacing.sm) {
168
- Image(systemName: provider.icon)
165
+ Image(systemName: self.provider.icon)
169
166
  .font(.system(size: 24))
170
167
  .foregroundStyle(self.iconColor)
171
168
  .frame(width: 32, height: 32)
172
169
 
173
170
  VStack(alignment: .leading, spacing: 4) {
174
- Text(provider.name)
175
- .font(.body)
171
+ Text(self.provider.name)
172
+ .font(RunicFont.body)
176
173
  .fontWeight(.medium)
177
174
 
178
175
  if let snapshot = self.snapshot {
179
176
  if let error = snapshot.error {
180
177
  Text(error)
181
- .font(.caption)
178
+ .font(RunicFont.caption)
182
179
  .foregroundStyle(.red)
183
180
  } else {
184
181
  HStack(spacing: 4) {
185
182
  if let quota = snapshot.usageData.quota, let used = snapshot.usageData.used {
186
183
  let percent = quota > 0 ? (used / quota) * 100 : 0
187
184
  Text("\(Int(percent))% used")
188
- .font(.caption)
185
+ .font(RunicFont.caption)
189
186
  .foregroundStyle(.secondary)
190
187
  }
191
188
  Text("•")
192
- .font(.caption)
189
+ .font(RunicFont.caption)
193
190
  .foregroundStyle(.tertiary)
194
191
  Text("Updated \(snapshot.updatedAt.relativeDescription())")
195
- .font(.caption)
192
+ .font(RunicFont.caption)
196
193
  .foregroundStyle(.tertiary)
197
194
  }
198
195
  }
199
196
  } else {
200
197
  Text("No data")
201
- .font(.caption)
198
+ .font(RunicFont.caption)
202
199
  .foregroundStyle(.tertiary)
203
200
  }
204
201
  }
@@ -222,17 +219,16 @@ private struct CustomProviderRow: View {
222
219
 
223
220
  Toggle("", isOn: Binding(
224
221
  get: { self.provider.enabled },
225
- set: { self.onToggle($0) }
226
- ))
227
- .labelsHidden()
228
- .toggleStyle(.switch)
222
+ set: { self.onToggle($0) }))
223
+ .labelsHidden()
224
+ .toggleStyle(.switch)
229
225
  }
230
226
  }
231
227
  .padding(.vertical, RunicSpacing.sm)
232
228
  }
233
229
 
234
230
  private var iconColor: Color {
235
- if !provider.enabled { return .secondary }
231
+ if !self.provider.enabled { return .secondary }
236
232
  if let colorHex = provider.colorHex, let nsColor = NSColor(hexString: colorHex) {
237
233
  return Color(nsColor: nsColor)
238
234
  }
@@ -242,8 +238,8 @@ private struct CustomProviderRow: View {
242
238
 
243
239
  // MARK: - Helper Extensions
244
240
 
245
- private extension Date {
246
- func relativeDescription() -> String {
241
+ extension Date {
242
+ fileprivate func relativeDescription() -> String {
247
243
  let now = Date()
248
244
  let interval = now.timeIntervalSince(self)
249
245
  if interval < 60 { return "just now" }
@@ -253,8 +249,8 @@ private extension Date {
253
249
  }
254
250
  }
255
251
 
256
- private extension NSColor {
257
- convenience init?(hexString: String) {
252
+ extension NSColor {
253
+ fileprivate convenience init?(hexString: String) {
258
254
  var hex = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
259
255
  if hex.hasPrefix("#") { hex.removeFirst() }
260
256
  guard hex.count == 6 else { return nil }
@@ -267,7 +263,6 @@ private extension NSColor {
267
263
  }
268
264
  }
269
265
 
270
-
271
266
  // MARK: - Custom Provider Editor View (Simplified)
272
267
 
273
268
  @MainActor
@@ -283,39 +278,43 @@ private struct CustomProviderEditorView: View {
283
278
 
284
279
  var body: some View {
285
280
  VStack {
286
- Text(provider == nil ? "Add Custom Provider" : "Edit Provider")
287
- .font(.headline)
281
+ Text(self.provider == nil ? "Add Custom Provider" : "Edit Provider")
282
+ .font(RunicFont.headline)
288
283
  .padding()
289
284
  Form {
290
- TextField("Provider Name", text: $name)
291
- TextField("Icon (SF Symbol)", text: $icon)
292
- SecureField("API Token", text: $apiToken)
293
- TextField("Usage Endpoint URL", text: $usageEndpointURL)
285
+ TextField("Provider Name", text: self.$name)
286
+ TextField("Icon (SF Symbol)", text: self.$icon)
287
+ SecureField("API Token", text: self.$apiToken)
288
+ TextField("Usage Endpoint URL", text: self.$usageEndpointURL)
294
289
  }
295
290
  .padding()
296
291
  HStack {
297
- Button("Cancel") { onCancel() }
298
- Button(provider == nil ? "Add" : "Save") {
292
+ Button("Cancel") { self.onCancel() }
293
+ Button(self.provider == nil ? "Add" : "Save") {
299
294
  // Simplified save logic
300
- onSave(CustomProviderConfig(
301
- id: provider?.id ?? UUID().uuidString,
302
- name: name,
303
- icon: icon,
304
- enabled: provider?.enabled ?? true,
305
- auth: AuthConfig(type: .bearer, headerName: "Authorization", tokenKeychain: "custom-\(name)"),
306
- endpoints: EndpointConfig(usage: UsageEndpoint(url: usageEndpointURL, mapping: ResponseMapping()))
307
- ))
295
+ self.onSave(CustomProviderConfig(
296
+ id: self.provider?.id ?? UUID().uuidString,
297
+ name: self.name,
298
+ icon: self.icon,
299
+ enabled: self.provider?.enabled ?? true,
300
+ auth: AuthConfig(
301
+ type: .bearer,
302
+ headerName: "Authorization",
303
+ tokenKeychain: "custom-\(self.name)"),
304
+ endpoints: EndpointConfig(usage: UsageEndpoint(
305
+ url: self.usageEndpointURL,
306
+ mapping: ResponseMapping()))))
308
307
  }
309
- .disabled(name.isEmpty || apiToken.isEmpty || usageEndpointURL.isEmpty)
308
+ .disabled(self.name.isEmpty || self.apiToken.isEmpty || self.usageEndpointURL.isEmpty)
310
309
  }
311
310
  .padding()
312
311
  }
313
312
  .frame(width: 500, height: 400)
314
313
  .onAppear {
315
314
  if let p = provider {
316
- name = p.name
317
- icon = p.icon
318
- usageEndpointURL = p.endpoints.usage?.url ?? ""
315
+ self.name = p.name
316
+ self.icon = p.icon
317
+ self.usageEndpointURL = p.endpoints.usage?.url ?? ""
319
318
  }
320
319
  }
321
320
  }
@@ -128,7 +128,7 @@ struct DebugPane: View {
128
128
  ZStack(alignment: .topLeading) {
129
129
  ScrollView {
130
130
  Text(self.displayedLog)
131
- .font(.system(.footnote, design: .monospaced))
131
+ .font(RunicFont.footnote)
132
132
  .textSelection(.enabled)
133
133
  .frame(maxWidth: .infinity, alignment: .leading)
134
134
  .padding(RunicSpacing.xs)
@@ -160,7 +160,7 @@ struct DebugPane: View {
160
160
 
161
161
  ScrollView {
162
162
  Text(self.fetchAttemptsText(for: self.currentFetchProvider))
163
- .font(.system(.footnote, design: .monospaced))
163
+ .font(RunicFont.footnote)
164
164
  .textSelection(.enabled)
165
165
  .frame(maxWidth: .infinity, alignment: .leading)
166
166
  .padding(RunicSpacing.xs)
@@ -185,7 +185,7 @@ struct DebugPane: View {
185
185
  Text(self.store.openAIDashboardCookieImportDebugLog?.isEmpty == false
186
186
  ? (self.store.openAIDashboardCookieImportDebugLog ?? "")
187
187
  : "No log yet. Enable \"Access OpenAI via web\" in General to run an import.")
188
- .font(.system(.footnote, design: .monospaced))
188
+ .font(RunicFont.footnote)
189
189
  .textSelection(.enabled)
190
190
  .frame(maxWidth: .infinity, alignment: .leading)
191
191
  .padding(RunicSpacing.xs)
@@ -212,7 +212,7 @@ struct DebugPane: View {
212
212
 
213
213
  if let status = self.costCacheStatus {
214
214
  Text(status)
215
- .font(.footnote)
215
+ .font(RunicFont.footnote)
216
216
  .foregroundStyle(.tertiary)
217
217
  }
218
218
  }
@@ -313,12 +313,12 @@ struct DebugPane: View {
313
313
 
314
314
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
315
315
  Text("Effective PATH")
316
- .font(.callout.weight(.semibold))
316
+ .font(RunicFont.callout.weight(.semibold))
317
317
  ScrollView {
318
318
  Text(self.store.pathDebugInfo.effectivePATH.isEmpty
319
319
  ? "Unavailable"
320
320
  : self.store.pathDebugInfo.effectivePATH)
321
- .font(.system(.footnote, design: .monospaced))
321
+ .font(RunicFont.footnote)
322
322
  .textSelection(.enabled)
323
323
  .frame(maxWidth: .infinity, alignment: .leading)
324
324
  .padding(RunicSpacing.xs)
@@ -331,10 +331,10 @@ struct DebugPane: View {
331
331
  if let loginPATH = self.store.pathDebugInfo.loginShellPATH {
332
332
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
333
333
  Text("Login shell PATH (startup capture)")
334
- .font(.callout.weight(.semibold))
334
+ .font(RunicFont.callout.weight(.semibold))
335
335
  ScrollView {
336
336
  Text(loginPATH)
337
- .font(.system(.footnote, design: .monospaced))
337
+ .font(RunicFont.footnote)
338
338
  .textSelection(.enabled)
339
339
  .frame(maxWidth: .infinity, alignment: .leading)
340
340
  .padding(RunicSpacing.xs)
@@ -405,9 +405,9 @@ struct DebugPane: View {
405
405
  private func binaryRow(title: String, value: String?) -> some View {
406
406
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
407
407
  Text(title)
408
- .font(.callout.weight(.semibold))
408
+ .font(RunicFont.callout.weight(.semibold))
409
409
  Text(value ?? "Not found")
410
- .font(.system(.footnote, design: .monospaced))
410
+ .font(RunicFont.footnote)
411
411
  .foregroundStyle(value == nil ? .secondary : .primary)
412
412
  }
413
413
  }
@@ -23,18 +23,18 @@ struct GeneralPane: View {
23
23
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
24
24
  Toggle(isOn: self.$settings.costUsageEnabled) {
25
25
  Text("Show cost summary")
26
- .font(.body)
26
+ .font(RunicFont.body)
27
27
  }
28
28
  .toggleStyle(.checkbox)
29
29
 
30
30
  Text("Reads local usage logs. Shows today + last 30 days cost in the menu.")
31
- .font(.footnote)
31
+ .font(RunicFont.footnote)
32
32
  .foregroundStyle(.tertiary)
33
33
  .fixedSize(horizontal: false, vertical: true)
34
34
 
35
35
  if self.settings.costUsageEnabled {
36
36
  Text("Auto-refresh: hourly · Timeout: 10m")
37
- .font(.footnote)
37
+ .font(RunicFont.footnote)
38
38
  .foregroundStyle(.tertiary)
39
39
 
40
40
  self.costStatusLine(provider: .claude)
@@ -45,12 +45,12 @@ struct GeneralPane: View {
45
45
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
46
46
  Toggle(isOn: self.$settings.openAIWebAccessEnabled) {
47
47
  Text("Access OpenAI via web")
48
- .font(.body)
48
+ .font(RunicFont.body)
49
49
  }
50
50
  .toggleStyle(.checkbox)
51
51
 
52
52
  Text("Imports browser cookies for dashboard extras (credits history, code review).")
53
- .font(.footnote)
53
+ .font(RunicFont.footnote)
54
54
  .foregroundStyle(.tertiary)
55
55
  .fixedSize(horizontal: false, vertical: true)
56
56
  }
@@ -84,7 +84,7 @@ struct GeneralPane: View {
84
84
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
85
85
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
86
86
  Text("Menu refresh rate")
87
- .font(.body)
87
+ .font(RunicFont.body)
88
88
 
89
89
  Picker("", selection: self.$settings.refreshFrequency) {
90
90
  ForEach(RefreshFrequency.allCases) { option in
@@ -94,13 +94,13 @@ struct GeneralPane: View {
94
94
  .pickerStyle(.segmented)
95
95
 
96
96
  Text("How often to automatically refresh usage data.")
97
- .font(.footnote)
97
+ .font(RunicFont.footnote)
98
98
  .foregroundStyle(.tertiary)
99
99
  }
100
100
 
101
101
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
102
102
  Text("Chart style")
103
- .font(.body)
103
+ .font(RunicFont.body)
104
104
 
105
105
  Picker("", selection: self.$settings.chartStyle) {
106
106
  Text("Line").tag(ChartStyle.line)
@@ -111,13 +111,13 @@ struct GeneralPane: View {
111
111
  .frame(maxWidth: 360)
112
112
 
113
113
  Text("Visual style for usage charts and graphs.")
114
- .font(.footnote)
114
+ .font(RunicFont.footnote)
115
115
  .foregroundStyle(.tertiary)
116
116
  }
117
117
 
118
118
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
119
119
  Text("Number format")
120
- .font(.body)
120
+ .font(RunicFont.body)
121
121
 
122
122
  Picker("", selection: self.$settings.numberFormat) {
123
123
  Text("Abbreviated (45.2K)").tag(NumberFormat.abbreviated)
@@ -127,13 +127,13 @@ struct GeneralPane: View {
127
127
  .frame(maxWidth: 360)
128
128
 
129
129
  Text("How to display large numbers in the UI.")
130
- .font(.footnote)
130
+ .font(RunicFont.footnote)
131
131
  .foregroundStyle(.tertiary)
132
132
  }
133
133
 
134
134
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
135
135
  Text("Date format")
136
- .font(.body)
136
+ .font(RunicFont.body)
137
137
 
138
138
  Picker("", selection: self.$settings.dateFormat) {
139
139
  Text("Relative (2h ago)").tag(DateFormat.relative)
@@ -143,13 +143,13 @@ struct GeneralPane: View {
143
143
  .frame(maxWidth: 360)
144
144
 
145
145
  Text("How to display timestamps throughout the app.")
146
- .font(.footnote)
146
+ .font(RunicFont.footnote)
147
147
  .foregroundStyle(.tertiary)
148
148
  }
149
149
 
150
150
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
151
151
  Text("Theme")
152
- .font(.body)
152
+ .font(RunicFont.body)
153
153
 
154
154
  Picker("", selection: self.$settings.theme) {
155
155
  Text("System").tag(Theme.system)
@@ -160,7 +160,26 @@ struct GeneralPane: View {
160
160
  .frame(maxWidth: 360)
161
161
 
162
162
  Text("App appearance (requires restart).")
163
- .font(.footnote)
163
+ .font(RunicFont.footnote)
164
+ .foregroundStyle(.tertiary)
165
+ }
166
+
167
+ VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
168
+ Text("Font")
169
+ .font(RunicFont.body)
170
+
171
+ Picker("", selection: self.$settings.selectedFontFamily) {
172
+ ForEach(RunicFontChoice.availableChoices()) { choice in
173
+ Text(choice.displayName)
174
+ .font(choice.previewFont)
175
+ .tag(choice.id)
176
+ }
177
+ }
178
+ .pickerStyle(.menu)
179
+ .frame(maxWidth: 360)
180
+
181
+ Text("Drop .ttf/.otf files in Resources/Fonts to add more.")
182
+ .font(RunicFont.footnote)
164
183
  .foregroundStyle(.tertiary)
165
184
  }
166
185
  }
@@ -186,7 +205,7 @@ struct GeneralPane: View {
186
205
 
187
206
  guard provider == .claude || provider == .codex else {
188
207
  return Text("\(name): unsupported")
189
- .font(.footnote)
208
+ .font(RunicFont.footnote)
190
209
  .foregroundStyle(.tertiary)
191
210
  }
192
211
 
@@ -200,20 +219,20 @@ struct GeneralPane: View {
200
219
  return formatter.string(from: seconds).map { " (\($0))" } ?? ""
201
220
  }()
202
221
  return Text("\(name): fetching…\(elapsed)")
203
- .font(.footnote)
222
+ .font(RunicFont.footnote)
204
223
  .foregroundStyle(.tertiary)
205
224
  }
206
225
  if let snapshot = self.store.tokenSnapshot(for: provider) {
207
226
  let updated = UsageFormatter.updatedString(from: snapshot.updatedAt)
208
227
  let cost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
209
228
  return Text("\(name): \(updated) · 30d \(cost)")
210
- .font(.footnote)
229
+ .font(RunicFont.footnote)
211
230
  .foregroundStyle(.tertiary)
212
231
  }
213
232
  if let error = self.store.tokenError(for: provider), !error.isEmpty {
214
233
  let truncated = UsageFormatter.truncatedSingleLine(error, max: 120)
215
234
  return Text("\(name): \(truncated)")
216
- .font(.footnote)
235
+ .font(RunicFont.footnote)
217
236
  .foregroundStyle(.tertiary)
218
237
  }
219
238
  if let lastAttempt = self.store.tokenLastAttemptAt(for: provider) {
@@ -221,11 +240,11 @@ struct GeneralPane: View {
221
240
  rel.unitsStyle = .abbreviated
222
241
  let when = rel.localizedString(for: lastAttempt, relativeTo: Date())
223
242
  return Text("\(name): last attempt \(when)")
224
- .font(.footnote)
243
+ .font(RunicFont.footnote)
225
244
  .foregroundStyle(.tertiary)
226
245
  }
227
246
  return Text("\(name): no data yet")
228
- .font(.footnote)
247
+ .font(RunicFont.footnote)
229
248
  .foregroundStyle(.tertiary)
230
249
  }
231
250
  }
@@ -7,22 +7,22 @@ struct HelpPane: View {
7
7
  PreferencesPane {
8
8
  SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
9
9
  Text("Menu refresh")
10
- .font(.caption)
10
+ .font(RunicFont.caption)
11
11
  .foregroundStyle(.secondary)
12
12
  .textCase(.uppercase)
13
13
 
14
14
  Text("Use Ping now in the menu to update usage without closing the panel.")
15
- .font(.body)
15
+ .font(RunicFont.body)
16
16
  .foregroundStyle(.secondary)
17
17
  .fixedSize(horizontal: false, vertical: true)
18
18
 
19
19
  Text("Auto-refresh cadence and safety rules live in Performance > Refresh & Safety.")
20
- .font(.body)
20
+ .font(RunicFont.body)
21
21
  .foregroundStyle(.secondary)
22
22
  .fixedSize(horizontal: false, vertical: true)
23
23
 
24
24
  Text("Manual mode avoids background refreshes that may touch tokens.")
25
- .font(.body)
25
+ .font(RunicFont.body)
26
26
  .foregroundStyle(.secondary)
27
27
  .fixedSize(horizontal: false, vertical: true)
28
28
  }
@@ -31,17 +31,17 @@ struct HelpPane: View {
31
31
 
32
32
  SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
33
33
  Text("Data sources")
34
- .font(.caption)
34
+ .font(RunicFont.caption)
35
35
  .foregroundStyle(.secondary)
36
36
  .textCase(.uppercase)
37
37
 
38
38
  Text("Runic reads local logs or browser cookies depending on provider settings.")
39
- .font(.body)
39
+ .font(RunicFont.body)
40
40
  .foregroundStyle(.secondary)
41
41
  .fixedSize(horizontal: false, vertical: true)
42
42
 
43
43
  Text("Open Providers to review each source and add credentials.")
44
- .font(.body)
44
+ .font(RunicFont.body)
45
45
  .foregroundStyle(.secondary)
46
46
  .fixedSize(horizontal: false, vertical: true)
47
47
  }