@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
@@ -46,7 +46,7 @@ public enum UsageLedgerAggregator {
46
46
  timeZone: TimeZone,
47
47
  groupByProject: Bool = true) -> [UsageLedgerDailySummary]
48
48
  {
49
- let calendar = calendarFor(timeZone)
49
+ let calendar = self.calendarFor(timeZone)
50
50
  var buckets: [DailyKey: AggregateAccumulator] = [:]
51
51
 
52
52
  for entry in entries {
@@ -59,7 +59,7 @@ public enum UsageLedgerAggregator {
59
59
  }
60
60
 
61
61
  return buckets.map { key, acc in
62
- let dayKey = dayKeyString(from: key.dayStart, timeZone: timeZone)
62
+ let dayKey = self.dayKeyString(from: key.dayStart, timeZone: timeZone)
63
63
  return UsageLedgerDailySummary(
64
64
  provider: key.provider,
65
65
  projectID: key.projectID,
@@ -138,7 +138,10 @@ public enum UsageLedgerAggregator {
138
138
  let isActive = now <= blockEnd && now.timeIntervalSince(lastTimestamp) <= blockDuration
139
139
  let acc = AggregateAccumulator(entries: currentEntries)
140
140
  let tokensPerMinute = isActive ? acc.tokensPerMinute(since: currentStart, now: now) : nil
141
- let projected = isActive ? acc.projectedTotalTokens(since: currentStart, now: now, duration: blockDuration) : nil
141
+ let projected = isActive ? acc.projectedTotalTokens(
142
+ since: currentStart,
143
+ now: now,
144
+ duration: blockDuration) : nil
142
145
  summaries.append(UsageLedgerBlockSummary(
143
146
  provider: key.provider,
144
147
  sessionID: key.sessionID,
@@ -283,7 +286,7 @@ public enum UsageLedgerAggregator {
283
286
  groupByProject: Bool) -> [UsageLedgerSpendForecast]
284
287
  {
285
288
  guard projectionDays > 0 else { return [] }
286
- let calendar = calendarFor(timeZone)
289
+ let calendar = self.calendarFor(timeZone)
287
290
  var buckets: [SpendForecastKey: SpendForecastAccumulator] = [:]
288
291
 
289
292
  for entry in entries {
@@ -334,7 +337,7 @@ public enum UsageLedgerAggregator {
334
337
 
335
338
  private static func dayKeyString(from date: Date, timeZone: TimeZone) -> String {
336
339
  let formatter = DateFormatter()
337
- formatter.calendar = calendarFor(timeZone)
340
+ formatter.calendar = self.calendarFor(timeZone)
338
341
  formatter.locale = Locale(identifier: "en_US_POSIX")
339
342
  formatter.timeZone = timeZone
340
343
  formatter.dateFormat = "yyyy-MM-dd"
@@ -343,7 +346,7 @@ public enum UsageLedgerAggregator {
343
346
 
344
347
  private static func hourKeyString(from date: Date, timeZone: TimeZone) -> String {
345
348
  let formatter = DateFormatter()
346
- formatter.calendar = calendarFor(timeZone)
349
+ formatter.calendar = self.calendarFor(timeZone)
347
350
  formatter.locale = Locale(identifier: "en_US_POSIX")
348
351
  formatter.timeZone = timeZone
349
352
  formatter.dateFormat = "yyyy-MM-dd'T'HH:00:00"
@@ -355,7 +358,7 @@ public enum UsageLedgerAggregator {
355
358
  timeZone: TimeZone,
356
359
  groupByProject: Bool = true) -> [UsageLedgerHourlySummary]
357
360
  {
358
- let calendar = calendarFor(timeZone)
361
+ let calendar = self.calendarFor(timeZone)
359
362
  var buckets: [HourlyKey: AggregateAccumulator] = [:]
360
363
 
361
364
  for entry in entries {
@@ -371,7 +374,7 @@ public enum UsageLedgerAggregator {
371
374
  }
372
375
 
373
376
  return buckets.map { key, acc in
374
- let hourKey = hourKeyString(from: key.hourStart, timeZone: timeZone)
377
+ let hourKey = self.hourKeyString(from: key.hourStart, timeZone: timeZone)
375
378
  return UsageLedgerHourlySummary(
376
379
  provider: key.provider,
377
380
  projectID: key.projectID,
@@ -445,7 +448,10 @@ private struct AggregateAccumulator {
445
448
  self.modelSet.sorted()
446
449
  }
447
450
 
448
- private mutating func mergeProjectIdentity(from identity: UsageLedgerProjectIdentity?, fallbackEntry: UsageLedgerEntry) {
451
+ private mutating func mergeProjectIdentity(
452
+ from identity: UsageLedgerProjectIdentity?,
453
+ fallbackEntry: UsageLedgerEntry)
454
+ {
449
455
  if let key = identity?.key, self.projectKey == nil {
450
456
  self.projectKey = key
451
457
  }
@@ -468,17 +474,16 @@ private struct AggregateAccumulator {
468
474
  let candidateProvenance = identity?.provenance
469
475
 
470
476
  guard let candidateName, !candidateName.isEmpty else { return }
471
- let shouldAdopt: Bool
472
- if self.projectName == nil {
473
- shouldAdopt = true
477
+ let shouldAdopt = if self.projectName == nil {
478
+ true
474
479
  } else if candidateConfidence.rank > self.projectNameConfidence.rank {
475
- shouldAdopt = true
480
+ true
476
481
  } else if candidateConfidence.rank == self.projectNameConfidence.rank,
477
482
  candidateName.count > (self.projectName?.count ?? 0)
478
483
  {
479
- shouldAdopt = true
484
+ true
480
485
  } else {
481
- shouldAdopt = false
486
+ false
482
487
  }
483
488
 
484
489
  if shouldAdopt {
@@ -586,7 +591,10 @@ private struct SpendForecastAccumulator {
586
591
  return lowerValue + ((upperValue - lowerValue) * weight)
587
592
  }
588
593
 
589
- private mutating func mergeProjectIdentity(from identity: UsageLedgerProjectIdentity?, fallbackEntry: UsageLedgerEntry) {
594
+ private mutating func mergeProjectIdentity(
595
+ from identity: UsageLedgerProjectIdentity?,
596
+ fallbackEntry: UsageLedgerEntry)
597
+ {
590
598
  if self.projectID == nil {
591
599
  let preferredID = identity?.projectID?.trimmingCharacters(in: .whitespacesAndNewlines)
592
600
  if let preferredID, !preferredID.isEmpty {
@@ -605,17 +613,16 @@ private struct SpendForecastAccumulator {
605
613
  let candidateProvenance = identity?.provenance
606
614
 
607
615
  guard let candidateName, !candidateName.isEmpty else { return }
608
- let shouldAdopt: Bool
609
- if self.projectName == nil {
610
- shouldAdopt = true
616
+ let shouldAdopt = if self.projectName == nil {
617
+ true
611
618
  } else if candidateConfidence.rank > self.projectNameConfidence.rank {
612
- shouldAdopt = true
619
+ true
613
620
  } else if candidateConfidence.rank == self.projectNameConfidence.rank,
614
621
  candidateName.count > (self.projectName?.count ?? 0)
615
622
  {
616
- shouldAdopt = true
623
+ true
617
624
  } else {
618
- shouldAdopt = false
625
+ false
619
626
  }
620
627
 
621
628
  if shouldAdopt {
@@ -654,7 +661,15 @@ private struct SessionAccumulator {
654
661
  }
655
662
  }
656
663
 
657
- var totals: UsageLedgerTotals { self.aggregate.totals }
658
- var models: [String] { self.modelSet.sorted() }
659
- var versions: [String] { self.versionSet.sorted() }
664
+ var totals: UsageLedgerTotals {
665
+ self.aggregate.totals
666
+ }
667
+
668
+ var models: [String] {
669
+ self.modelSet.sorted()
670
+ }
671
+
672
+ var versions: [String] {
673
+ self.versionSet.sorted()
674
+ }
660
675
  }
@@ -97,7 +97,7 @@ public enum UsageLedgerInsightsAdvisor {
97
97
  score -= 10
98
98
  signals.append("No project-level grouping is available.")
99
99
  } else {
100
- let unknownProjects = projectBreakdown.filter { $0.displayProjectName == "Unknown project" }.count
100
+ let unknownProjects = projectBreakdown.count(where: { $0.displayProjectName == "Unknown project" })
101
101
  let unknownRatio = Double(unknownProjects) / Double(max(1, projectBreakdown.count))
102
102
  if unknownRatio > 0.40 {
103
103
  score -= 10
@@ -60,11 +60,11 @@ public enum UsageLedgerProjectIdentityResolver {
60
60
  projectName: String?,
61
61
  budgetNameOverride: String? = nil) -> UsageLedgerProjectIdentity
62
62
  {
63
- let normalizedProjectID = normalizedIdentifier(projectID)
64
- let normalizedProjectName = normalizedDisplay(projectName)
65
- let budgetName = normalizedDisplay(budgetNameOverride)
66
- let workspaceCandidate = firstPathLikeValue(candidates: [projectID, projectName])
67
- let workspaceHash = workspaceCandidate.map(stablePathHash)
63
+ let normalizedProjectID = self.normalizedIdentifier(projectID)
64
+ let normalizedProjectName = self.normalizedDisplay(projectName)
65
+ let budgetName = self.normalizedDisplay(budgetNameOverride)
66
+ let workspaceCandidate = self.firstPathLikeValue(candidates: [projectID, projectName])
67
+ let workspaceHash = workspaceCandidate.map(self.stablePathHash)
68
68
  let workspaceSuffix = workspaceHash.map { "#w:\($0.prefix(8))" } ?? ""
69
69
 
70
70
  var displayName: String?
@@ -103,7 +103,7 @@ public enum UsageLedgerProjectIdentityResolver {
103
103
  if let normalizedProjectID {
104
104
  key = "id:\(normalizedProjectID.lowercased())\(workspaceSuffix)"
105
105
  } else if let seed = displayName {
106
- let slug = slugify(seed)
106
+ let slug = self.slugify(seed)
107
107
  key = slug.isEmpty ? nil : "name:\(slug)\(workspaceSuffix)"
108
108
  } else {
109
109
  key = nil
@@ -129,11 +129,11 @@ public enum UsageLedgerProjectIdentityResolver {
129
129
  if normalized.contains("/") || normalized.contains("\\") {
130
130
  return nil
131
131
  }
132
- return readableIdentifier(normalized)
132
+ return self.readableIdentifier(normalized)
133
133
  }
134
134
 
135
135
  private static func normalizedIdentifier(_ value: String?) -> String? {
136
- let cleaned = normalizedDisplay(value)
136
+ let cleaned = self.normalizedDisplay(value)
137
137
  guard let cleaned, !cleaned.isEmpty else { return nil }
138
138
  return cleaned
139
139
  }
@@ -149,6 +149,43 @@ public enum UsageLedgerProjectIdentityResolver {
149
149
 
150
150
  private static func inferredPathTail(from value: String?) -> String? {
151
151
  guard let value else { return nil }
152
+
153
+ // Claude Code uses dash-separated path IDs like "-Users-srinivaspendela-Sriinnu-AI-Runic".
154
+ // Extract the last meaningful segment(s) as the project name.
155
+ if value.hasPrefix("-Users-") || value.hasPrefix("-home-") {
156
+ let parts = value.split(separator: "-").map(String.init)
157
+ // Find the last non-user-path segment — skip Users, username, common dirs
158
+ let skipPrefixes: Set = [
159
+ "Users",
160
+ "home",
161
+ "Documents",
162
+ "Desktop",
163
+ "Projects",
164
+ "Personal",
165
+ "repos",
166
+ "code",
167
+ "src",
168
+ "dev",
169
+ "Work",
170
+ "Sites",
171
+ ]
172
+ var meaningful: [String] = []
173
+ var pastUserPath = false
174
+ for part in parts where !part.isEmpty {
175
+ if !pastUserPath {
176
+ if skipPrefixes.contains(part) || part.count <= 2 { continue }
177
+ pastUserPath = true
178
+ }
179
+ meaningful.append(part)
180
+ }
181
+ // Take the last 1-2 segments as the project name
182
+ if meaningful.count >= 2 {
183
+ return meaningful.suffix(2).joined(separator: "/")
184
+ } else if let last = meaningful.last {
185
+ return last
186
+ }
187
+ }
188
+
152
189
  let separators = CharacterSet(charactersIn: "/\\")
153
190
  guard value.rangeOfCharacter(from: separators) != nil else { return nil }
154
191
 
@@ -156,23 +193,23 @@ public enum UsageLedgerProjectIdentityResolver {
156
193
  ch == "/" || ch == "\\"
157
194
  }
158
195
  guard let last = parts.last else { return nil }
159
- let tail = normalizedDisplay(String(last))
196
+ let tail = self.normalizedDisplay(String(last))
160
197
  guard let tail else { return nil }
161
- guard !looksOpaqueIdentifier(tail) else { return nil }
198
+ guard !self.looksOpaqueIdentifier(tail) else { return nil }
162
199
  return tail
163
200
  }
164
201
 
165
202
  private static func readableIdentifier(_ value: String?) -> String? {
166
203
  guard let value else { return nil }
167
204
  guard !value.isEmpty else { return nil }
168
- guard !looksOpaqueIdentifier(value) else { return nil }
205
+ guard !self.looksOpaqueIdentifier(value) else { return nil }
169
206
  return value
170
207
  }
171
208
 
172
209
  private static func inferredNameToken(from value: String?) -> String? {
173
- let normalized = normalizedDisplay(value)
210
+ let normalized = self.normalizedDisplay(value)
174
211
  guard let normalized, !normalized.isEmpty else { return nil }
175
- let slug = slugify(normalized)
212
+ let slug = self.slugify(normalized)
176
213
  guard !slug.isEmpty else { return nil }
177
214
  let titleCased = slug.split(separator: "-")
178
215
  .map { token -> String in
@@ -197,7 +234,9 @@ public enum UsageLedgerProjectIdentityResolver {
197
234
  }
198
235
 
199
236
  private static func slugify(_ value: String) -> String {
200
- let folded = value.folding(options: [.diacriticInsensitive, .caseInsensitive, .widthInsensitive], locale: .init(identifier: "en_US_POSIX"))
237
+ let folded = value.folding(
238
+ options: [.diacriticInsensitive, .caseInsensitive, .widthInsensitive],
239
+ locale: .init(identifier: "en_US_POSIX"))
201
240
  var scalarBuffer: [UnicodeScalar] = []
202
241
  scalarBuffer.reserveCapacity(folded.unicodeScalars.count)
203
242
 
@@ -235,10 +274,10 @@ public enum UsageLedgerProjectIdentityResolver {
235
274
 
236
275
  private static func stablePathHash(_ value: String) -> String {
237
276
  let normalized = value.lowercased()
238
- var hash: UInt64 = 0xcbf29ce484222325
277
+ var hash: UInt64 = 0xCBF2_9CE4_8422_2325
239
278
  for byte in normalized.utf8 {
240
279
  hash ^= UInt64(byte)
241
- hash = hash &* 0x100000001b3
280
+ hash = hash &* 0x100_0000_01B3
242
281
  }
243
282
  return String(format: "%016llx", hash)
244
283
  }
@@ -43,21 +43,21 @@ enum CostUsageCacheIO {
43
43
  }
44
44
  }
45
45
 
46
- struct CostUsageCache: Codable, Sendable {
46
+ struct CostUsageCache: Codable {
47
47
  var version: Int = 1
48
48
  var lastScanUnixMs: Int64 = 0
49
49
 
50
- // filePath -> file usage
50
+ /// filePath -> file usage
51
51
  var files: [String: CostUsageFileUsage] = [:]
52
52
 
53
- // dayKey -> model -> packed usage
53
+ /// dayKey -> model -> packed usage
54
54
  var days: [String: [String: [Int]]] = [:]
55
55
 
56
- // rootPath -> mtime (for Claude roots)
56
+ /// rootPath -> mtime (for Claude roots)
57
57
  var roots: [String: Int64]?
58
58
  }
59
59
 
60
- struct CostUsageFileUsage: Codable, Sendable {
60
+ struct CostUsageFileUsage: Codable {
61
61
  var mtimeUnixMs: Int64
62
62
  var size: Int64
63
63
  var days: [String: [String: [Int]]]
@@ -66,7 +66,7 @@ struct CostUsageFileUsage: Codable, Sendable {
66
66
  var lastTotals: CostUsageCodexTotals?
67
67
  }
68
68
 
69
- struct CostUsageCodexTotals: Codable, Sendable {
69
+ struct CostUsageCodexTotals: Codable {
70
70
  var input: Int
71
71
  var cached: Int
72
72
  var output: Int
@@ -1,7 +1,7 @@
1
1
  import Foundation
2
2
 
3
3
  enum CostUsageJsonl {
4
- struct Line: Sendable {
4
+ struct Line {
5
5
  let bytes: Data
6
6
  let wasTruncated: Bool
7
7
  }
@@ -1,13 +1,13 @@
1
1
  import Foundation
2
2
 
3
3
  enum CostUsagePricing {
4
- struct CodexPricing: Sendable {
4
+ struct CodexPricing {
5
5
  let inputCostPerToken: Double
6
6
  let outputCostPerToken: Double
7
7
  let cacheReadInputCostPerToken: Double
8
8
  }
9
9
 
10
- struct ClaudePricing: Sendable {
10
+ struct ClaudePricing {
11
11
  let inputCostPerToken: Double
12
12
  let outputCostPerToken: Double
13
13
  let cacheCreationInputCostPerToken: Double
@@ -1,12 +1,12 @@
1
1
  import Foundation
2
2
 
3
3
  enum CostUsageScanner {
4
- struct Options: Sendable {
4
+ struct Options {
5
5
  var codexSessionsRoot: URL?
6
6
  var claudeProjectsRoots: [URL]?
7
7
  var cacheRoot: URL?
8
8
  var refreshMinIntervalSeconds: TimeInterval = 60
9
- // Force a full rescan, ignoring per-file cache and incremental offsets.
9
+ /// Force a full rescan, ignoring per-file cache and incremental offsets.
10
10
  var forceRescan: Bool = false
11
11
 
12
12
  init(
@@ -22,14 +22,14 @@ enum CostUsageScanner {
22
22
  }
23
23
  }
24
24
 
25
- struct CodexParseResult: Sendable {
25
+ struct CodexParseResult {
26
26
  let days: [String: [String: [Int]]]
27
27
  let parsedBytes: Int64
28
28
  let lastModel: String?
29
29
  let lastTotals: CostUsageCodexTotals?
30
30
  }
31
31
 
32
- struct ClaudeParseResult: Sendable {
32
+ struct ClaudeParseResult {
33
33
  let days: [String: [String: [Int]]]
34
34
  let parsedBytes: Int64
35
35
  }
@@ -101,7 +101,7 @@ enum CostUsageScanner {
101
101
 
102
102
  // MARK: - Day keys
103
103
 
104
- struct CostUsageDayRange: Sendable {
104
+ struct CostUsageDayRange {
105
105
  let sinceKey: String
106
106
  let untilKey: String
107
107
  let scanSinceKey: String
@@ -4,10 +4,9 @@ import Testing
4
4
  @testable import Runic
5
5
 
6
6
  @MainActor
7
- @Suite
8
7
  struct AppDelegateTests {
9
8
  @Test
10
- func buildsStatusControllerAfterLaunch() {
9
+ func `builds status controller after launch`() {
11
10
  let appDelegate = AppDelegate()
12
11
  var factoryCalls = 0
13
12
 
@@ -18,7 +17,13 @@ struct AppDelegateTests {
18
17
  }
19
18
  defer { StatusItemController.factory = StatusItemController.defaultFactory }
20
19
 
21
- let settings = SettingsStore(zaiTokenStore: NoopZaiTokenStore(), minimaxTokenStore: NoopMiniMaxTokenStore(), minimaxCookieHeaderStore: NoopMiniMaxCookieHeaderStore(), minimaxGroupIDStore: NoopMiniMaxGroupIDStore(), openRouterTokenStore: NoopOpenRouterTokenStore(), groqTokenStore: NoopGroqTokenStore())
20
+ let settings = SettingsStore(
21
+ zaiTokenStore: NoopZaiTokenStore(),
22
+ minimaxTokenStore: NoopMiniMaxTokenStore(),
23
+ minimaxCookieHeaderStore: NoopMiniMaxCookieHeaderStore(),
24
+ minimaxGroupIDStore: NoopMiniMaxGroupIDStore(),
25
+ openRouterTokenStore: NoopOpenRouterTokenStore(),
26
+ groqTokenStore: NoopGroqTokenStore())
22
27
  let fetcher = UsageFetcher()
23
28
  let store = UsageStore(fetcher: fetcher, settings: settings)
24
29
  let account = fetcher.loadAccountInfo()
@@ -2,18 +2,17 @@ import RunicCore
2
2
  import Silo
3
3
  import Testing
4
4
 
5
- @Suite
6
5
  struct BrowserCookieOrderStatusStringTests {
7
6
  #if os(macOS)
8
7
  @Test
9
- func cursorNoSessionIncludesBrowserLoginHint() {
8
+ func `cursor no session includes browser login hint`() {
10
9
  let order = ProviderDefaults.metadata[.cursor]?.browserCookieOrder ?? Browser.defaultImportOrder
11
10
  let message = CursorStatusProbeError.noSessionCookie.errorDescription ?? ""
12
11
  #expect(message.contains(order.loginHint))
13
12
  }
14
13
 
15
14
  @Test
16
- func factoryNoSessionIncludesBrowserLoginHint() {
15
+ func `factory no session includes browser login hint`() {
17
16
  let order = ProviderDefaults.metadata[.factory]?.browserCookieOrder ?? Browser.defaultImportOrder
18
17
  let message = FactoryStatusProbeError.noSessionCookie.errorDescription ?? ""
19
18
  #expect(message.contains(order.loginHint))
@@ -1,10 +1,9 @@
1
1
  import Helix
2
2
  import Testing
3
3
 
4
- @Suite
5
4
  struct CLIArgumentParsingTests {
6
5
  @Test
7
- func usageCommandParsesProviderAndJsonFlags() throws {
6
+ func `usage command parses provider and json flags`() throws {
8
7
  let signature = CommandSignature(
9
8
  options: [
10
9
  OptionDefinition(label: "provider", names: [.long("provider")], help: nil),
@@ -25,7 +24,7 @@ struct CLIArgumentParsingTests {
25
24
  }
26
25
 
27
26
  @Test
28
- func costCommandParsesRefreshFlag() throws {
27
+ func `cost command parses refresh flag`() throws {
29
28
  let signature = CommandSignature(
30
29
  options: [
31
30
  OptionDefinition(label: "provider", names: [.long("provider")], help: nil),
@@ -41,7 +40,7 @@ struct CLIArgumentParsingTests {
41
40
  }
42
41
 
43
42
  @Test
44
- func insightsCommandParsesViewAndBudgetFlags() throws {
43
+ func `insights command parses view and budget flags`() throws {
45
44
  let signature = CommandSignature(
46
45
  options: [
47
46
  OptionDefinition(label: "view", names: [.long("view")], help: nil),
@@ -2,26 +2,25 @@ import Foundation
2
2
  import RunicCore
3
3
  import Testing
4
4
 
5
- @Suite
6
5
  struct CLICostTests {
7
6
  @Test
8
- func costSnapshotStoresSessionAndMonthFields() {
7
+ func `cost snapshot stores session and month fields`() {
9
8
  let snapshot = CostUsageTokenSnapshot(
10
- sessionTokens: 1_200,
9
+ sessionTokens: 1200,
11
10
  sessionCostUSD: 1.25,
12
- last30DaysTokens: 9_000,
11
+ last30DaysTokens: 9000,
13
12
  last30DaysCostUSD: 9.99,
14
13
  daily: [],
15
14
  updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
16
15
 
17
- #expect(snapshot.sessionTokens == 1_200)
16
+ #expect(snapshot.sessionTokens == 1200)
18
17
  #expect(snapshot.sessionCostUSD == 1.25)
19
- #expect(snapshot.last30DaysTokens == 9_000)
18
+ #expect(snapshot.last30DaysTokens == 9000)
20
19
  #expect(snapshot.last30DaysCostUSD == 9.99)
21
20
  }
22
21
 
23
22
  @Test
24
- func dailyReportDecodesLegacyCacheTokenKeys() throws {
23
+ func `daily report decodes legacy cache token keys`() throws {
25
24
  let json = """
26
25
  {
27
26
  "daily": [
@@ -1,10 +1,9 @@
1
1
  import RunicCore
2
2
  import Testing
3
3
 
4
- @Suite
5
4
  struct CLIProviderSelectionTests {
6
5
  @Test
7
- func providerRegistryIncludesCoreProviders() {
6
+ func `provider registry includes core providers`() {
8
7
  let providers = Set(ProviderDescriptorRegistry.all.map(\.id))
9
8
 
10
9
  #expect(providers.contains(.codex))
@@ -15,7 +14,7 @@ struct CLIProviderSelectionTests {
15
14
  }
16
15
 
17
16
  @Test
18
- func providerEnumResolvesInsightsSupportedValues() {
17
+ func `provider enum resolves insights supported values`() {
19
18
  #expect(UsageProvider(rawValue: "codex") == .codex)
20
19
  #expect(UsageProvider(rawValue: "claude") == .claude)
21
20
  }
@@ -3,10 +3,9 @@ import RunicCore
3
3
  import Testing
4
4
  @testable import RunicCLI
5
5
 
6
- @Suite
7
6
  struct CLISnapshotTests {
8
7
  @Test
9
- func rendersCodexTextSnapshot() {
8
+ func `renders codex text snapshot`() {
10
9
  let identity = ProviderIdentitySnapshot(
11
10
  providerID: .codex,
12
11
  accountEmail: "user@example.com",
@@ -34,7 +33,7 @@ struct CLISnapshotTests {
34
33
  }
35
34
 
36
35
  @Test
37
- func rendersClaudeSnapshotWithoutWeeklyWhenMissing() {
36
+ func `renders claude snapshot without weekly when missing`() {
38
37
  let snapshot = UsageSnapshot(
39
38
  primary: .init(usedPercent: 2, windowMinutes: nil, resetsAt: nil, resetDescription: "3pm (Europe/Vienna)"),
40
39
  secondary: nil,
@@ -52,7 +51,7 @@ struct CLISnapshotTests {
52
51
  }
53
52
 
54
53
  @Test
55
- func appliesAnsiColorsWhenEnabled() {
54
+ func `applies ansi colors when enabled`() {
56
55
  let snapshot = UsageSnapshot(
57
56
  primary: .init(usedPercent: 95, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
58
57
  secondary: .init(usedPercent: 80, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
@@ -2,7 +2,6 @@ import Testing
2
2
  @testable import RunicCLI
3
3
  @testable import RunicCore
4
4
 
5
- @Suite
6
5
  struct CLIWebFallbackTests {
7
6
  private func makeContext(sourceMode: ProviderSourceMode = .auto) -> ProviderFetchContext {
8
7
  ProviderFetchContext(
@@ -19,7 +18,7 @@ struct CLIWebFallbackTests {
19
18
  }
20
19
 
21
20
  @Test
22
- func codexFallsBackWhenCookiesMissing() {
21
+ func `codex falls back when cookies missing`() {
23
22
  let context = self.makeContext()
24
23
  let strategy = CodexWebDashboardStrategy()
25
24
  #expect(strategy.shouldFallback(
@@ -40,7 +39,7 @@ struct CLIWebFallbackTests {
40
39
  }
41
40
 
42
41
  @Test
43
- func codexDoesNotFallbackForDashboardDataErrors() {
42
+ func `codex does not fallback for dashboard data errors`() {
44
43
  let context = self.makeContext()
45
44
  let strategy = CodexWebDashboardStrategy()
46
45
  #expect(!strategy.shouldFallback(
@@ -49,7 +48,7 @@ struct CLIWebFallbackTests {
49
48
  }
50
49
 
51
50
  @Test
52
- func claudeFallsBackWhenNoSessionKey() {
51
+ func `claude falls back when no session key`() {
53
52
  let context = self.makeContext()
54
53
  let strategy = ClaudeWebFetchStrategy()
55
54
  #expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.noSessionKeyFound, context: context))