@sriinnu/runic 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +1 -1
  3. package/README.md +47 -29
  4. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  5. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  6. package/RuniciOS/Views/ProviderListView.swift +10 -10
  7. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  8. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  9. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  10. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  11. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  12. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  13. package/Scripts/package_app.sh +2 -2
  14. package/Sources/Runic/About.swift +3 -3
  15. package/Sources/Runic/AppNotifications.swift +5 -3
  16. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
  17. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  18. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  19. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  20. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
  21. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  22. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  23. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  24. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  25. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  26. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  28. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
  29. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  30. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  31. package/Sources/Runic/Core/RunicColors.swift +6 -6
  32. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  33. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  34. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
  35. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
  36. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
  37. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
  38. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
  39. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  40. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
  41. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
  42. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
  43. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
  44. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
  45. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
  46. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
  47. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
  48. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
  49. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
  50. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  51. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
  52. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
  53. package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
  54. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
  55. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  56. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  57. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  58. package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
  59. package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
  60. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
  61. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  62. package/Sources/Runic/ProviderRegistry.swift +1 -1
  63. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  81. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  83. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  84. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  85. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  87. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  88. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  107. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  108. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  109. package/Sources/Runic/Resources/provider-context-windows.json +20 -0
  110. package/Sources/Runic/RunicApp.swift +46 -36
  111. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  112. package/Sources/Runic/UsagePaceText.swift +1 -1
  113. package/Sources/Runic/UsageProgressBar.swift +13 -9
  114. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  115. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  116. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  117. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  118. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  119. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  120. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  121. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  122. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  123. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
  124. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  125. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  126. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  127. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  128. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  129. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  130. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  131. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  132. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
  133. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  134. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  135. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  136. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  137. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  138. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  139. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
  140. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  141. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  142. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  143. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  144. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  145. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  146. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  147. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  148. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  149. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  150. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  151. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  152. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  153. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  154. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
  155. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  156. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  157. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  158. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  159. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  160. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  161. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  162. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  163. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  164. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  165. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  166. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  167. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  168. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  169. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  170. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  171. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  172. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  173. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  174. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  175. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  176. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  177. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
  178. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  179. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  180. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  181. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
  182. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  183. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  184. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  185. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  186. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  187. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  188. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  189. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  190. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  191. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  192. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  193. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  194. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
  195. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  196. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  197. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  198. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  199. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  200. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  201. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  202. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  204. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
  205. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
  206. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  207. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  208. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  209. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  210. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  211. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  212. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  213. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  214. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  215. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  216. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
  217. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  218. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  219. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  220. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  221. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
  222. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  225. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
  226. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  227. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  228. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  229. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  230. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  231. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
  232. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  233. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  234. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  235. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  236. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
  237. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  240. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  241. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  242. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
  243. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  244. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  245. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  246. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  247. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  248. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  249. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  250. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  251. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  252. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  253. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
  254. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  255. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  256. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  257. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  258. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  259. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  260. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  261. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  262. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  263. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  264. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  265. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
  266. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
  267. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
  268. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  269. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  270. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  271. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  272. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  273. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  274. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  275. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  276. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  277. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  278. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  279. package/Tests/RunicTests/CLICostTests.swift +6 -7
  280. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  281. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  282. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  283. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  284. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  285. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  286. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  287. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  288. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  289. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  290. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  291. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  292. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  293. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  294. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  295. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  296. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  297. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  298. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  299. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  300. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  301. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  302. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  303. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  304. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  305. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  306. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  307. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  308. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  309. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  310. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  311. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  312. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  313. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  314. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  315. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  316. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  317. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  318. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  319. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  320. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  321. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  322. package/Tests/RunicTests/RunicTests.swift +6 -7
  323. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  324. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  325. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  326. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  327. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  328. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  329. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  330. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  331. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  332. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  333. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  334. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  335. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  336. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  337. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  338. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  339. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  340. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  341. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  342. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  343. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  344. package/appcast.xml +36 -37
  345. package/package.json +1 -1
  346. package/runic.png +0 -0
  347. package/runic.svg +15 -0
  348. package/version.env +2 -2
@@ -1,6 +1,6 @@
1
1
  import AppKit
2
- import RunicCore
3
2
  import Observation
3
+ import RunicCore
4
4
  import ServiceManagement
5
5
 
6
6
  enum RefreshFrequency: String, CaseIterable, Identifiable {
@@ -10,7 +10,9 @@ enum RefreshFrequency: String, CaseIterable, Identifiable {
10
10
  case fiveMinutes
11
11
  case fifteenMinutes
12
12
 
13
- var id: String { self.rawValue }
13
+ var id: String {
14
+ self.rawValue
15
+ }
14
16
 
15
17
  var seconds: TimeInterval? {
16
18
  switch self {
@@ -38,7 +40,9 @@ enum UsageMetricDisplayMode: String, CaseIterable, Identifiable {
38
40
  case barsOnly
39
41
  case percentOnly
40
42
 
41
- var id: String { self.rawValue }
43
+ var id: String {
44
+ self.rawValue
45
+ }
42
46
 
43
47
  var label: String {
44
48
  switch self {
@@ -72,7 +76,9 @@ enum MenuMode: String, CaseIterable, Identifiable {
72
76
  case analyst
73
77
  case `operator`
74
78
 
75
- var id: String { self.rawValue }
79
+ var id: String {
80
+ self.rawValue
81
+ }
76
82
 
77
83
  var label: String {
78
84
  switch self {
@@ -80,7 +86,7 @@ enum MenuMode: String, CaseIterable, Identifiable {
80
86
  "Glance"
81
87
  case .analyst:
82
88
  "Analyst"
83
- case .`operator`:
89
+ case .operator:
84
90
  "Operator"
85
91
  }
86
92
  }
@@ -91,13 +97,15 @@ enum ChartStyle: String, CaseIterable, Identifiable {
91
97
  case area
92
98
  case bar
93
99
 
94
- var id: String { self.rawValue }
100
+ var id: String {
101
+ self.rawValue
102
+ }
95
103
 
96
104
  var label: String {
97
105
  switch self {
98
- case .line: return "Line"
99
- case .area: return "Area"
100
- case .bar: return "Bar"
106
+ case .line: "Line"
107
+ case .area: "Area"
108
+ case .bar: "Bar"
101
109
  }
102
110
  }
103
111
  }
@@ -106,12 +114,14 @@ enum NumberFormat: String, CaseIterable, Identifiable {
106
114
  case abbreviated
107
115
  case full
108
116
 
109
- var id: String { self.rawValue }
117
+ var id: String {
118
+ self.rawValue
119
+ }
110
120
 
111
121
  var label: String {
112
122
  switch self {
113
- case .abbreviated: return "Abbreviated"
114
- case .full: return "Full"
123
+ case .abbreviated: "Abbreviated"
124
+ case .full: "Full"
115
125
  }
116
126
  }
117
127
  }
@@ -120,12 +130,14 @@ enum DateFormat: String, CaseIterable, Identifiable {
120
130
  case relative
121
131
  case absolute
122
132
 
123
- var id: String { self.rawValue }
133
+ var id: String {
134
+ self.rawValue
135
+ }
124
136
 
125
137
  var label: String {
126
138
  switch self {
127
- case .relative: return "Relative"
128
- case .absolute: return "Absolute"
139
+ case .relative: "Relative"
140
+ case .absolute: "Absolute"
129
141
  }
130
142
  }
131
143
  }
@@ -135,13 +147,15 @@ enum Theme: String, CaseIterable, Identifiable {
135
147
  case light
136
148
  case dark
137
149
 
138
- var id: String { self.rawValue }
150
+ var id: String {
151
+ self.rawValue
152
+ }
139
153
 
140
154
  var label: String {
141
155
  switch self {
142
- case .system: return "System"
143
- case .light: return "Light"
144
- case .dark: return "Dark"
156
+ case .system: "System"
157
+ case .light: "Light"
158
+ case .dark: "Dark"
145
159
  }
146
160
  }
147
161
  }
@@ -150,12 +164,14 @@ enum ProviderSwitcherLayout: String, CaseIterable, Identifiable {
150
164
  case top
151
165
  case sidebar
152
166
 
153
- var id: String { self.rawValue }
167
+ var id: String {
168
+ self.rawValue
169
+ }
154
170
 
155
171
  var label: String {
156
172
  switch self {
157
- case .top: return "Top"
158
- case .sidebar: return "Sidebar"
173
+ case .top: "Top"
174
+ case .sidebar: "Sidebar"
159
175
  }
160
176
  }
161
177
  }
@@ -164,12 +180,14 @@ enum ProviderSwitcherIconSize: String, CaseIterable, Identifiable {
164
180
  case small
165
181
  case medium
166
182
 
167
- var id: String { self.rawValue }
183
+ var id: String {
184
+ self.rawValue
185
+ }
168
186
 
169
187
  var label: String {
170
188
  switch self {
171
- case .small: return "Small"
172
- case .medium: return "Medium"
189
+ case .small: "Small"
190
+ case .medium: "Medium"
173
191
  }
174
192
  }
175
193
  }
@@ -189,15 +207,21 @@ final class SettingsStore {
189
207
  }
190
208
 
191
209
  var autoDisableRefreshWhenIdleEnabled: Bool {
192
- didSet { self.userDefaults.set(self.autoDisableRefreshWhenIdleEnabled, forKey: "autoDisableRefreshWhenIdleEnabled") }
210
+ didSet { self.userDefaults.set(
211
+ self.autoDisableRefreshWhenIdleEnabled,
212
+ forKey: "autoDisableRefreshWhenIdleEnabled") }
193
213
  }
194
214
 
195
215
  var autoDisableRefreshWhenIdleMinutes: Int {
196
- didSet { self.userDefaults.set(self.autoDisableRefreshWhenIdleMinutes, forKey: "autoDisableRefreshWhenIdleMinutes") }
216
+ didSet { self.userDefaults.set(
217
+ self.autoDisableRefreshWhenIdleMinutes,
218
+ forKey: "autoDisableRefreshWhenIdleMinutes") }
197
219
  }
198
220
 
199
221
  var autoDisableRefreshOnSleepEnabled: Bool {
200
- didSet { self.userDefaults.set(self.autoDisableRefreshOnSleepEnabled, forKey: "autoDisableRefreshOnSleepEnabled") }
222
+ didSet {
223
+ self.userDefaults.set(self.autoDisableRefreshOnSleepEnabled, forKey: "autoDisableRefreshOnSleepEnabled")
224
+ }
201
225
  }
202
226
 
203
227
  var autoRefreshWarningEnabled: Bool {
@@ -210,13 +234,17 @@ final class SettingsStore {
210
234
 
211
235
  var autoSuspendInactiveProvidersEnabled: Bool {
212
236
  didSet {
213
- self.userDefaults.set(self.autoSuspendInactiveProvidersEnabled, forKey: "autoSuspendInactiveProvidersEnabled")
237
+ self.userDefaults.set(
238
+ self.autoSuspendInactiveProvidersEnabled,
239
+ forKey: "autoSuspendInactiveProvidersEnabled")
214
240
  }
215
241
  }
216
242
 
217
243
  var autoSuspendInactiveProvidersMinutes: Int {
218
244
  didSet {
219
- self.userDefaults.set(self.autoSuspendInactiveProvidersMinutes, forKey: "autoSuspendInactiveProvidersMinutes")
245
+ self.userDefaults.set(
246
+ self.autoSuspendInactiveProvidersMinutes,
247
+ forKey: "autoSuspendInactiveProvidersMinutes")
220
248
  }
221
249
  }
222
250
 
@@ -227,7 +255,8 @@ final class SettingsStore {
227
255
  }
228
256
  }
229
257
 
230
- /// Hidden toggle to reveal debug-only menu items (enable via defaults write com.sriinnu.athena.Runic debugMenuEnabled
258
+ /// Hidden toggle to reveal debug-only menu items (enable via defaults write com.sriinnu.athena.Runic
259
+ /// debugMenuEnabled
231
260
  /// -bool YES).
232
261
  var debugMenuEnabled: Bool {
233
262
  didSet { self.userDefaults.set(self.debugMenuEnabled, forKey: "debugMenuEnabled") }
@@ -296,6 +325,13 @@ final class SettingsStore {
296
325
  }
297
326
  }
298
327
 
328
+ var selectedFontFamily: String {
329
+ didSet {
330
+ self.userDefaults.set(self.selectedFontFamily, forKey: "selectedFontFamily")
331
+ RunicFont.family = self.selectedFontFamily
332
+ }
333
+ }
334
+
299
335
  /// Optional: use provider branding icons with a percentage in the menu bar.
300
336
  var menuBarShowsBrandIconWithPercent: Bool {
301
337
  didSet {
@@ -325,6 +361,12 @@ final class SettingsStore {
325
361
  didSet { self.userDefaults.set(self.insightsReportDays, forKey: "insightsReportDays") }
326
362
  }
327
363
 
364
+ /// How many days of usage history to scan for ledger data (charts, breakdowns).
365
+ /// Options: 3, 7, 30, 365. Default: 30.
366
+ var ledgerMaxAgeDays: Int {
367
+ didSet { self.userDefaults.set(self.ledgerMaxAgeDays, forKey: "ledgerMaxAgeDays") }
368
+ }
369
+
328
370
  var randomBlinkEnabled: Bool {
329
371
  didSet { self.userDefaults.set(self.randomBlinkEnabled, forKey: "randomBlinkEnabled") }
330
372
  }
@@ -400,7 +442,7 @@ final class SettingsStore {
400
442
  var zaiAPIToken: String {
401
443
  didSet {
402
444
  self.schedulePersistZaiAPIToken()
403
- if !zaiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
445
+ if !self.zaiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
404
446
  self.autoEnableProviderIfNeeded(cliName: "zai")
405
447
  }
406
448
  }
@@ -410,7 +452,7 @@ final class SettingsStore {
410
452
  var minimaxAPIToken: String {
411
453
  didSet {
412
454
  self.schedulePersistMiniMaxAPIToken()
413
- if !minimaxAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
455
+ if !self.minimaxAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
414
456
  self.autoEnableProviderIfNeeded(cliName: "minimax")
415
457
  }
416
458
  }
@@ -420,7 +462,7 @@ final class SettingsStore {
420
462
  var minimaxCookieHeader: String {
421
463
  didSet {
422
464
  self.schedulePersistMiniMaxCookieHeader()
423
- if !minimaxCookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
465
+ if !self.minimaxCookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
424
466
  self.autoEnableProviderIfNeeded(cliName: "minimax")
425
467
  }
426
468
  }
@@ -435,7 +477,7 @@ final class SettingsStore {
435
477
  var copilotAPIToken: String {
436
478
  didSet {
437
479
  self.schedulePersistCopilotAPIToken()
438
- if !copilotAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
480
+ if !self.copilotAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
439
481
  self.autoEnableProviderIfNeeded(cliName: "copilot")
440
482
  }
441
483
  }
@@ -445,7 +487,7 @@ final class SettingsStore {
445
487
  var openRouterAPIToken: String {
446
488
  didSet {
447
489
  self.schedulePersistOpenRouterAPIToken()
448
- if !openRouterAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
490
+ if !self.openRouterAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
449
491
  self.autoEnableProviderIfNeeded(cliName: "openrouter")
450
492
  }
451
493
  }
@@ -455,7 +497,7 @@ final class SettingsStore {
455
497
  var groqAPIToken: String {
456
498
  didSet {
457
499
  self.schedulePersistGroqAPIToken()
458
- if !groqAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
500
+ if !self.groqAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
459
501
  self.autoEnableProviderIfNeeded(cliName: "groq")
460
502
  }
461
503
  }
@@ -465,7 +507,7 @@ final class SettingsStore {
465
507
  var deepSeekAPIToken: String {
466
508
  didSet {
467
509
  self.schedulePersistDeepSeekAPIToken()
468
- if !deepSeekAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
510
+ if !self.deepSeekAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
469
511
  self.autoEnableProviderIfNeeded(cliName: "deepseek")
470
512
  }
471
513
  }
@@ -475,7 +517,7 @@ final class SettingsStore {
475
517
  var fireworksAPIToken: String {
476
518
  didSet {
477
519
  self.schedulePersistFireworksAPIToken()
478
- if !fireworksAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
520
+ if !self.fireworksAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
479
521
  self.autoEnableProviderIfNeeded(cliName: "fireworks")
480
522
  }
481
523
  }
@@ -485,7 +527,7 @@ final class SettingsStore {
485
527
  var mistralAPIToken: String {
486
528
  didSet {
487
529
  self.schedulePersistMistralAPIToken()
488
- if !mistralAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
530
+ if !self.mistralAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
489
531
  self.autoEnableProviderIfNeeded(cliName: "mistral")
490
532
  }
491
533
  }
@@ -495,7 +537,7 @@ final class SettingsStore {
495
537
  var perplexityAPIToken: String {
496
538
  didSet {
497
539
  self.schedulePersistPerplexityAPIToken()
498
- if !perplexityAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
540
+ if !self.perplexityAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
499
541
  self.autoEnableProviderIfNeeded(cliName: "perplexity")
500
542
  }
501
543
  }
@@ -505,7 +547,7 @@ final class SettingsStore {
505
547
  var kimiAPIToken: String {
506
548
  didSet {
507
549
  self.schedulePersistKimiAPIToken()
508
- if !kimiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
550
+ if !self.kimiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
509
551
  self.autoEnableProviderIfNeeded(cliName: "kimi")
510
552
  }
511
553
  }
@@ -515,7 +557,7 @@ final class SettingsStore {
515
557
  var auggieAPIToken: String {
516
558
  didSet {
517
559
  self.schedulePersistAuggieAPIToken()
518
- if !auggieAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
560
+ if !self.auggieAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
519
561
  self.autoEnableProviderIfNeeded(cliName: "auggie")
520
562
  }
521
563
  }
@@ -525,7 +567,7 @@ final class SettingsStore {
525
567
  var togetherAPIToken: String {
526
568
  didSet {
527
569
  self.schedulePersistTogetherAPIToken()
528
- if !togetherAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
570
+ if !self.togetherAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
529
571
  self.autoEnableProviderIfNeeded(cliName: "together")
530
572
  }
531
573
  }
@@ -535,7 +577,7 @@ final class SettingsStore {
535
577
  var cohereAPIToken: String {
536
578
  didSet {
537
579
  self.schedulePersistCohereAPIToken()
538
- if !cohereAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
580
+ if !self.cohereAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
539
581
  self.autoEnableProviderIfNeeded(cliName: "cohere")
540
582
  }
541
583
  }
@@ -545,7 +587,7 @@ final class SettingsStore {
545
587
  var xaiAPIToken: String {
546
588
  didSet {
547
589
  self.schedulePersistXAiAPIToken()
548
- if !xaiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
590
+ if !self.xaiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
549
591
  self.autoEnableProviderIfNeeded(cliName: "xai")
550
592
  }
551
593
  }
@@ -555,7 +597,7 @@ final class SettingsStore {
555
597
  var cerebrasAPIToken: String {
556
598
  didSet {
557
599
  self.schedulePersistCerebrasAPIToken()
558
- if !cerebrasAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
600
+ if !self.cerebrasAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
559
601
  self.autoEnableProviderIfNeeded(cliName: "cerebras")
560
602
  }
561
603
  }
@@ -565,7 +607,7 @@ final class SettingsStore {
565
607
  var qwenAPIToken: String {
566
608
  didSet {
567
609
  self.schedulePersistQwenAPIToken()
568
- if !qwenAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
610
+ if !self.qwenAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
569
611
  self.autoEnableProviderIfNeeded(cliName: "qwen")
570
612
  }
571
613
  }
@@ -575,7 +617,7 @@ final class SettingsStore {
575
617
  var sambaNovaAPIToken: String {
576
618
  didSet {
577
619
  self.schedulePersistSambaNovaAPIToken()
578
- if !sambaNovaAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
620
+ if !self.sambaNovaAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
579
621
  self.autoEnableProviderIfNeeded(cliName: "sambanova")
580
622
  }
581
623
  }
@@ -600,7 +642,7 @@ final class SettingsStore {
600
642
  var azureOpenAIAPIToken: String {
601
643
  didSet {
602
644
  self.schedulePersistAzureOpenAIAPIToken()
603
- if !azureOpenAIAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
645
+ if !self.azureOpenAIAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
604
646
  self.autoEnableProviderIfNeeded(cliName: "azure")
605
647
  }
606
648
  }
@@ -699,6 +741,7 @@ final class SettingsStore {
699
741
  _ = self.costUsageEnabled
700
742
  _ = self.insightsMenuMaxItems
701
743
  _ = self.insightsReportDays
744
+ _ = self.ledgerMaxAgeDays
702
745
  _ = self.randomBlinkEnabled
703
746
  _ = self.claudeWebExtrasEnabled
704
747
  _ = self.showOptionalCreditsAndExtraUsage
@@ -877,7 +920,7 @@ final class SettingsStore {
877
920
  let metricDisplayRaw = userDefaults.string(forKey: "usageMetricDisplayMode")
878
921
  self.usageMetricDisplayMode = UsageMetricDisplayMode(rawValue: metricDisplayRaw ?? "") ?? .barsAndPercent
879
922
  let menuModeRaw = userDefaults.string(forKey: "menuMode")
880
- self.menuMode = MenuMode(rawValue: menuModeRaw ?? "") ?? .`operator`
923
+ self.menuMode = MenuMode(rawValue: menuModeRaw ?? "") ?? .operator
881
924
  let chartStyleRaw = userDefaults.string(forKey: "chartStyle")
882
925
  self.chartStyle = ChartStyle(rawValue: chartStyleRaw ?? "") ?? .line
883
926
  let numberFormatRaw = userDefaults.string(forKey: "numberFormat")
@@ -886,6 +929,7 @@ final class SettingsStore {
886
929
  self.dateFormat = DateFormat(rawValue: dateFormatRaw ?? "") ?? .relative
887
930
  let themeRaw = userDefaults.string(forKey: "theme")
888
931
  self.theme = Theme(rawValue: themeRaw ?? "") ?? .system
932
+ self.selectedFontFamily = userDefaults.string(forKey: "selectedFontFamily") ?? "Fira Code"
889
933
  self.menuBarShowsBrandIconWithPercent = userDefaults.object(
890
934
  forKey: "menuBarShowsBrandIconWithPercent") as? Bool ?? false
891
935
  self.menuBarVibrantIconEnabled = userDefaults.object(
@@ -893,6 +937,7 @@ final class SettingsStore {
893
937
  self.costUsageEnabled = userDefaults.object(forKey: "tokenCostUsageEnabled") as? Bool ?? true
894
938
  self.insightsMenuMaxItems = userDefaults.object(forKey: "insightsMenuMaxItems") as? Int ?? 4
895
939
  self.insightsReportDays = userDefaults.object(forKey: "insightsReportDays") as? Int ?? 7
940
+ self.ledgerMaxAgeDays = userDefaults.object(forKey: "ledgerMaxAgeDays") as? Int ?? 30
896
941
  self.randomBlinkEnabled = userDefaults.object(forKey: "randomBlinkEnabled") as? Bool ?? false
897
942
  self.claudeWebExtrasEnabled = userDefaults.object(forKey: "claudeWebExtrasEnabled") as? Bool ?? false
898
943
  let creditsExtrasDefault = userDefaults.object(forKey: "showOptionalCreditsAndExtraUsage") as? Bool
@@ -945,7 +990,8 @@ final class SettingsStore {
945
990
  self.sambaNovaAPIToken = (try? sambaNovaTokenStore.loadToken()) ?? ""
946
991
  self.qwenAPIToken = (try? qwenTokenStore.loadToken()) ?? ""
947
992
  self.azureOpenAIAPIToken = (try? azureOpenAITokenStore.loadToken()) ?? ""
948
- self.selectedMenuProviderRaw = userDefaults.string(forKey: "selectedMenuProvider")
993
+ // Always start on Overview tab.
994
+ self.selectedMenuProviderRaw = nil
949
995
  self.providerDetectionCompleted = userDefaults.object(
950
996
  forKey: "providerDetectionCompleted") as? Bool ?? false
951
997
  self.toggleStore = ProviderToggleStore(userDefaults: userDefaults)
@@ -956,6 +1002,7 @@ final class SettingsStore {
956
1002
  if self.claudeUsageDataSource != .cli {
957
1003
  self.claudeWebExtrasEnabled = false
958
1004
  }
1005
+ RunicFont.family = self.selectedFontFamily
959
1006
  }
960
1007
 
961
1008
  func orderedProviders() -> [UsageProvider] {
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Security
4
4
 
5
5
  protocol TogetherTokenStoring: Sendable {
@@ -53,29 +53,16 @@ struct KeychainTogetherTokenStore: TogetherTokenStoring {
53
53
  }
54
54
 
55
55
  let data = cleaned!.data(using: .utf8)!
56
- let query = self.baseQuery(dataProtection: false)
57
- let attributes: [String: Any] = [
58
- kSecValueData as String: data,
59
- kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
- ]
61
-
62
- // Try update first.
63
- let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
- if updateStatus == errSecSuccess { return }
65
-
66
- if updateStatus != errSecItemNotFound {
67
- // Item exists but update failed — delete and re-add to reset ACL.
68
- try? self.deleteToken(dataProtection: false)
69
- }
70
56
 
71
- var addQuery = query
72
- for (key, value) in attributes { addQuery[key] = value }
57
+ // Always delete-then-add to ensure SecAccess ACL is set.
58
+ // SecItemUpdate does NOT update the ACL, so existing items with
59
+ // missing or stale ACLs would continue to prompt for passwords.
60
+ try? self.deleteToken(dataProtection: false)
61
+ try? self.deleteToken(dataProtection: true)
73
62
 
74
- // Grant the calling app permanent access so macOS never shows a
75
- // Keychain password dialog for this item.
76
- if let access = Self.createSelfAccess() {
77
- addQuery[kSecAttrAccess as String] = access
78
- }
63
+ var addQuery = self.baseQuery(dataProtection: false)
64
+ addQuery[kSecValueData as String] = data
65
+ addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
79
66
 
80
67
  let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
68
  guard addStatus == errSecSuccess else {
@@ -91,6 +78,7 @@ struct KeychainTogetherTokenStore: TogetherTokenStoring {
91
78
  var query = self.baseQuery(dataProtection: dataProtection)
92
79
  query[kSecMatchLimit as String] = kSecMatchLimitOne
93
80
  query[kSecReturnData as String] = true
81
+ query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
94
82
 
95
83
  let status = SecItemCopyMatching(query as CFDictionary, &result)
96
84
  if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
@@ -126,18 +114,4 @@ struct KeychainTogetherTokenStore: TogetherTokenStoring {
126
114
  }
127
115
  return q
128
116
  }
129
-
130
- /// Creates a SecAccess that grants the calling application permanent
131
- /// read/write access without triggering the macOS Keychain password dialog.
132
- private static func createSelfAccess() -> SecAccess? {
133
- var trustedSelf: SecTrustedApplication?
134
- guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
- let trustedSelf else { return nil }
136
- var access: SecAccess?
137
- guard SecAccessCreate(
138
- "Runic API Token" as CFString,
139
- [trustedSelf] as CFArray,
140
- &access) == errSecSuccess else { return nil }
141
- return access
142
- }
143
117
  }
@@ -1,13 +1,26 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
 
4
4
  extension UsageStore {
5
- var codexSnapshot: UsageSnapshot? { self.snapshots[.codex] }
6
- var claudeSnapshot: UsageSnapshot? { self.snapshots[.claude] }
7
- var lastCodexError: String? { self.errors[.codex] }
8
- var lastClaudeError: String? { self.errors[.claude] }
5
+ var codexSnapshot: UsageSnapshot? {
6
+ self.snapshots[.codex]
7
+ }
8
+
9
+ var claudeSnapshot: UsageSnapshot? {
10
+ self.snapshots[.claude]
11
+ }
9
12
 
10
- func error(for provider: UsageProvider) -> String? { self.errors[provider] }
13
+ var lastCodexError: String? {
14
+ self.errors[.codex]
15
+ }
16
+
17
+ var lastClaudeError: String? {
18
+ self.errors[.claude]
19
+ }
20
+
21
+ func error(for provider: UsageProvider) -> String? {
22
+ self.errors[provider]
23
+ }
11
24
 
12
25
  func status(for provider: UsageProvider) -> ProviderStatus? {
13
26
  guard self.statusChecksEnabled else { return nil }
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
 
4
4
  extension UsageStore {
5
5
  func tokenSnapshot(for provider: UsageProvider) -> CostUsageTokenSnapshot? {
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  #if canImport(WidgetKit)
4
4
  import WidgetKit
5
5
  #endif