@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,12 +1,10 @@
1
1
  import Foundation
2
2
  import Testing
3
-
4
3
  @testable import RunicCore
5
4
 
6
- @Suite
7
5
  struct CostUsageScannerTests {
8
6
  @Test
9
- func codexDailyReportParsesTokenCountsAndCaches() throws {
7
+ func `codex daily report parses token counts and caches`() throws {
10
8
  let env = try CostUsageTestEnvironment()
11
9
  defer { env.cleanup() }
12
10
 
@@ -91,7 +89,7 @@ struct CostUsageScannerTests {
91
89
  }
92
90
 
93
91
  @Test
94
- func claudeDailyReportParsesUsageAndCaches() throws {
92
+ func `claude daily report parses usage and caches`() throws {
95
93
  let env = try CostUsageTestEnvironment()
96
94
  defer { env.cleanup() }
97
95
 
@@ -138,7 +136,7 @@ struct CostUsageScannerTests {
138
136
  }
139
137
 
140
138
  @Test
141
- func claudeParsesLargeLinesWithUsageAtTail() throws {
139
+ func `claude parses large lines with usage at tail`() throws {
142
140
  let env = try CostUsageTestEnvironment()
143
141
  defer { env.cleanup() }
144
142
 
@@ -183,7 +181,7 @@ struct CostUsageScannerTests {
183
181
  }
184
182
 
185
183
  @Test
186
- func claudeDailyReportRefreshesWhenFileChanges() throws {
184
+ func `claude daily report refreshes when file changes`() throws {
187
185
  let env = try CostUsageTestEnvironment()
188
186
  defer { env.cleanup() }
189
187
 
@@ -248,7 +246,7 @@ struct CostUsageScannerTests {
248
246
  }
249
247
 
250
248
  @Test
251
- func codexIncrementalParsingUsesPreviousTotals() throws {
249
+ func `codex incremental parsing uses previous totals`() throws {
252
250
  let env = try CostUsageTestEnvironment()
253
251
  defer { env.cleanup() }
254
252
 
@@ -327,7 +325,7 @@ struct CostUsageScannerTests {
327
325
  }
328
326
 
329
327
  @Test
330
- func claudeIncrementalParsingReadsAppendedLinesOnly() throws {
328
+ func `claude incremental parsing reads appended lines only`() throws {
331
329
  let env = try CostUsageTestEnvironment()
332
330
  defer { env.cleanup() }
333
331
 
@@ -387,7 +385,7 @@ struct CostUsageScannerTests {
387
385
  }
388
386
 
389
387
  @Test
390
- func dayKeyFromTimestampMatchesISOParsing() {
388
+ func `day key from timestamp matches ISO parsing`() {
391
389
  let timestamps = [
392
390
  "2025-12-20T23:59:59Z",
393
391
  "2025-12-20T23:59:59+02:00",
@@ -401,7 +399,7 @@ struct CostUsageScannerTests {
401
399
  }
402
400
 
403
401
  @Test
404
- func claudeDeduplicatesStreamingChunks() throws {
402
+ func `claude deduplicates streaming chunks`() throws {
405
403
  let env = try CostUsageTestEnvironment()
406
404
  defer { env.cleanup() }
407
405
 
@@ -489,7 +487,7 @@ struct CostUsageScannerTests {
489
487
  }
490
488
 
491
489
  @Test
492
- func claudeCountsEntriesWithoutIdsAsSeparate() throws {
490
+ func `claude counts entries without ids as separate`() throws {
493
491
  let env = try CostUsageTestEnvironment()
494
492
  defer { env.cleanup() }
495
493
 
@@ -553,7 +551,7 @@ struct CostUsageScannerTests {
553
551
  }
554
552
 
555
553
  @Test
556
- func claudeCountsDifferentRequestIdsSeparately() throws {
554
+ func `claude counts different request ids separately`() throws {
557
555
  let env = try CostUsageTestEnvironment()
558
556
  defer { env.cleanup() }
559
557
 
@@ -2,12 +2,11 @@ import Foundation
2
2
  import Testing
3
3
  @testable import RunicCore
4
4
 
5
- @Suite
6
5
  struct CursorStatusProbeTests {
7
6
  // MARK: - Usage Summary Parsing
8
7
 
9
8
  @Test
10
- func parsesBasicUsageSummary() throws {
9
+ func `parses basic usage summary`() throws {
11
10
  let json = """
12
11
  {
13
12
  "billingCycleStart": "2025-01-01T00:00:00.000Z",
@@ -38,7 +37,7 @@ struct CursorStatusProbeTests {
38
37
  }
39
38
  }
40
39
  """
41
- let data = json.data(using: .utf8)!
40
+ let data = try #require(json.data(using: .utf8))
42
41
  let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
43
42
 
44
43
  #expect(summary.membershipType == "pro")
@@ -51,7 +50,7 @@ struct CursorStatusProbeTests {
51
50
  }
52
51
 
53
52
  @Test
54
- func parsesMinimalUsageSummary() throws {
53
+ func `parses minimal usage summary`() throws {
55
54
  let json = """
56
55
  {
57
56
  "membershipType": "hobby",
@@ -63,7 +62,7 @@ struct CursorStatusProbeTests {
63
62
  }
64
63
  }
65
64
  """
66
- let data = json.data(using: .utf8)!
65
+ let data = try #require(json.data(using: .utf8))
67
66
  let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
68
67
 
69
68
  #expect(summary.membershipType == "hobby")
@@ -73,7 +72,7 @@ struct CursorStatusProbeTests {
73
72
  }
74
73
 
75
74
  @Test
76
- func parsesEnterpriseUsageSummary() throws {
75
+ func `parses enterprise usage summary`() throws {
77
76
  let json = """
78
77
  {
79
78
  "membershipType": "enterprise",
@@ -88,7 +87,7 @@ struct CursorStatusProbeTests {
88
87
  }
89
88
  }
90
89
  """
91
- let data = json.data(using: .utf8)!
90
+ let data = try #require(json.data(using: .utf8))
92
91
  let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
93
92
 
94
93
  #expect(summary.membershipType == "enterprise")
@@ -99,7 +98,7 @@ struct CursorStatusProbeTests {
99
98
  // MARK: - User Info Parsing
100
99
 
101
100
  @Test
102
- func parsesUserInfo() throws {
101
+ func `parses user info`() throws {
103
102
  let json = """
104
103
  {
105
104
  "email": "user@example.com",
@@ -108,7 +107,7 @@ struct CursorStatusProbeTests {
108
107
  "sub": "auth0|12345"
109
108
  }
110
109
  """
111
- let data = json.data(using: .utf8)!
110
+ let data = try #require(json.data(using: .utf8))
112
111
  let userInfo = try JSONDecoder().decode(CursorUserInfo.self, from: data)
113
112
 
114
113
  #expect(userInfo.email == "user@example.com")
@@ -120,7 +119,7 @@ struct CursorStatusProbeTests {
120
119
  // MARK: - Snapshot Conversion
121
120
 
122
121
  @Test
123
- func prefersPlanRatioOverPercentField() {
122
+ func `prefers plan ratio over percent field`() {
124
123
  let snapshot = CursorStatusProbe()
125
124
  .parseUsageSummary(
126
125
  CursorUsageSummary(
@@ -149,7 +148,7 @@ struct CursorStatusProbeTests {
149
148
  }
150
149
 
151
150
  @Test
152
- func usesPercentFieldWhenLimitMissing() {
151
+ func `uses percent field when limit missing`() {
153
152
  let snapshot = CursorStatusProbe()
154
153
  .parseUsageSummary(
155
154
  CursorUsageSummary(
@@ -178,7 +177,7 @@ struct CursorStatusProbeTests {
178
177
  }
179
178
 
180
179
  @Test
181
- func convertsSnapshotToUsageSnapshot() {
180
+ func `converts snapshot to usage snapshot`() {
182
181
  let snapshot = CursorStatusSnapshot(
183
182
  planPercentUsed: 45.0,
184
183
  planUsedUSD: 22.50,
@@ -206,7 +205,7 @@ struct CursorStatusProbeTests {
206
205
  }
207
206
 
208
207
  @Test
209
- func usesIndividualOnDemandWhenNoTeamUsage() {
208
+ func `uses individual on demand when no team usage`() {
210
209
  let snapshot = CursorStatusSnapshot(
211
210
  planPercentUsed: 10.0,
212
211
  planUsedUSD: 5.0,
@@ -229,7 +228,7 @@ struct CursorStatusProbeTests {
229
228
  }
230
229
 
231
230
  @Test
232
- func formatsMembershipTypes() {
231
+ func `formats membership types`() {
233
232
  let testCases: [(input: String, expected: String)] = [
234
233
  ("pro", "Cursor Pro"),
235
234
  ("hobby", "Cursor Hobby"),
@@ -259,7 +258,7 @@ struct CursorStatusProbeTests {
259
258
  }
260
259
 
261
260
  @Test
262
- func handlesNilOnDemandLimit() {
261
+ func `handles nil on demand limit`() {
263
262
  let snapshot = CursorStatusSnapshot(
264
263
  planPercentUsed: 50.0,
265
264
  planUsedUSD: 25.0,
@@ -287,7 +286,7 @@ struct CursorStatusProbeTests {
287
286
  // MARK: - Session Store Serialization
288
287
 
289
288
  @Test
290
- func sessionStoreSavesAndLoadsCookies() async throws {
289
+ func `session store saves and loads cookies`() async {
291
290
  let store = CursorSessionStore.shared
292
291
 
293
292
  // Clear any existing cookies
@@ -2,10 +2,9 @@ import Foundation
2
2
  import Testing
3
3
  @testable import RunicCore
4
4
 
5
- @Suite
6
5
  struct FactoryStatusSnapshotTests {
7
6
  @Test
8
- func mapsUsageSnapshotWindowsAndLoginMethod() {
7
+ func `maps usage snapshot windows and login method`() {
9
8
  let periodEnd = Date(timeIntervalSince1970: 1_738_368_000) // Feb 1, 2025
10
9
  let snapshot = FactoryStatusSnapshot(
11
10
  standardUserTokens: 50,
@@ -33,7 +32,7 @@ struct FactoryStatusSnapshotTests {
33
32
  }
34
33
 
35
34
  @Test
36
- func treatsLargeAllowancesAsUnlimited() {
35
+ func `treats large allowances as unlimited`() {
37
36
  let snapshot = FactoryStatusSnapshot(
38
37
  standardUserTokens: 50_000_000,
39
38
  standardOrgTokens: 0,
@@ -56,10 +55,9 @@ struct FactoryStatusSnapshotTests {
56
55
  }
57
56
  }
58
57
 
59
- @Suite
60
58
  struct FactoryStatusProbeWorkOSTests {
61
59
  @Test
62
- func detectsMissingRefreshTokenPayload() {
60
+ func `detects missing refresh token payload`() {
63
61
  let payload = Data("""
64
62
  {"error":"invalid_request","error_description":"Missing refresh token."}
65
63
  """.utf8)
@@ -1,10 +1,9 @@
1
1
  import Testing
2
2
  @testable import Runic
3
3
 
4
- @Suite
5
4
  struct GeminiLoginAlertTests {
6
5
  @Test
7
- func returnsAlertForMissingBinary() {
6
+ func `returns alert for missing binary`() {
8
7
  let result = GeminiLoginRunner.Result(outcome: .missingBinary)
9
8
  let info = StatusItemController.geminiLoginAlertInfo(for: result)
10
9
  #expect(info?.title == "Gemini CLI not found")
@@ -12,7 +11,7 @@ struct GeminiLoginAlertTests {
12
11
  }
13
12
 
14
13
  @Test
15
- func returnsAlertForLaunchFailure() {
14
+ func `returns alert for launch failure`() {
16
15
  let result = GeminiLoginRunner.Result(outcome: .launchFailed("Boom"))
17
16
  let info = StatusItemController.geminiLoginAlertInfo(for: result)
18
17
  #expect(info?.title == "Could not open Terminal for Gemini")
@@ -20,7 +19,7 @@ struct GeminiLoginAlertTests {
20
19
  }
21
20
 
22
21
  @Test
23
- func returnsNilOnSuccess() {
22
+ func `returns nil on success`() {
24
23
  let result = GeminiLoginRunner.Result(outcome: .success)
25
24
  let info = StatusItemController.geminiLoginAlertInfo(for: result)
26
25
  #expect(info == nil)
@@ -1,11 +1,11 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
 
5
- @Suite("Gemini API", .serialized)
5
+ @Suite(.serialized)
6
6
  struct GeminiStatusProbeAPITests {
7
7
  @Test
8
- func missingCredentialsThrowsNotLoggedIn() async throws {
8
+ func `missing credentials throws not logged in`() async throws {
9
9
  let env = try GeminiTestEnvironment()
10
10
  defer { env.cleanup() }
11
11
 
@@ -16,7 +16,7 @@ struct GeminiStatusProbeAPITests {
16
16
  }
17
17
 
18
18
  @Test
19
- func rejectsApiKeyAuthType() async throws {
19
+ func `rejects api key auth type`() async throws {
20
20
  let env = try GeminiTestEnvironment()
21
21
  defer { env.cleanup() }
22
22
  try env.writeSettings(authType: "api-key")
@@ -28,7 +28,7 @@ struct GeminiStatusProbeAPITests {
28
28
  }
29
29
 
30
30
  @Test
31
- func rejectsVertexAuthType() async throws {
31
+ func `rejects vertex auth type`() async throws {
32
32
  let env = try GeminiTestEnvironment()
33
33
  defer { env.cleanup() }
34
34
  try env.writeSettings(authType: "vertex-ai")
@@ -40,7 +40,7 @@ struct GeminiStatusProbeAPITests {
40
40
  }
41
41
 
42
42
  @Test
43
- func refreshesExpiredTokenAndUpdatesStoredCredentials() async throws {
43
+ func `refreshes expired token and updates stored credentials`() async throws {
44
44
  let env = try GeminiTestEnvironment()
45
45
  defer { env.cleanup() }
46
46
  try env.writeCredentials(
@@ -112,7 +112,7 @@ struct GeminiStatusProbeAPITests {
112
112
  }
113
113
 
114
114
  @Test
115
- func failsRefreshWhenOAuthConfigMissing() async throws {
115
+ func `fails refresh when O auth config missing`() async throws {
116
116
  let env = try GeminiTestEnvironment()
117
117
  defer { env.cleanup() }
118
118
  try env.writeCredentials(
@@ -139,7 +139,7 @@ struct GeminiStatusProbeAPITests {
139
139
  }
140
140
 
141
141
  @Test
142
- func reportsApiErrors() async throws {
142
+ func `reports api errors`() async throws {
143
143
  let env = try GeminiTestEnvironment()
144
144
  defer { env.cleanup() }
145
145
  try env.writeCredentials(
@@ -175,7 +175,7 @@ struct GeminiStatusProbeAPITests {
175
175
  }
176
176
 
177
177
  @Test
178
- func reportsNotLoggedInWhenAccessTokenMissing() async throws {
178
+ func `reports not logged in when access token missing`() async throws {
179
179
  let env = try GeminiTestEnvironment()
180
180
  defer { env.cleanup() }
181
181
  try env.writeCredentials(
@@ -191,7 +191,7 @@ struct GeminiStatusProbeAPITests {
191
191
  }
192
192
 
193
193
  @Test
194
- func reportsNotLoggedInOn401() async throws {
194
+ func `reports not logged in on401`() async throws {
195
195
  let env = try GeminiTestEnvironment()
196
196
  defer { env.cleanup() }
197
197
  try env.writeCredentials(
@@ -227,7 +227,7 @@ struct GeminiStatusProbeAPITests {
227
227
  }
228
228
 
229
229
  @Test
230
- func reportsParseErrorsForInvalidPayload() async throws {
230
+ func `reports parse errors for invalid payload`() async throws {
231
231
  let env = try GeminiTestEnvironment()
232
232
  defer { env.cleanup() }
233
233
  try env.writeCredentials(
@@ -1,11 +1,11 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
 
5
- @Suite("Gemini Plan", .serialized)
5
+ @Suite(.serialized)
6
6
  struct GeminiStatusProbePlanTests {
7
7
  @Test
8
- func selectsProjectIdForQuotaRequests() async throws {
8
+ func `selects project id for quota requests`() async throws {
9
9
  let env = try GeminiTestEnvironment()
10
10
  defer { env.cleanup() }
11
11
  try env.writeCredentials(
@@ -56,7 +56,7 @@ struct GeminiStatusProbePlanTests {
56
56
  }
57
57
 
58
58
  @Test
59
- func detectsPaidFromStandardTier() async throws {
59
+ func `detects paid from standard tier`() async throws {
60
60
  let env = try GeminiTestEnvironment()
61
61
  defer { env.cleanup() }
62
62
  try env.writeCredentials(
@@ -100,7 +100,7 @@ struct GeminiStatusProbePlanTests {
100
100
  }
101
101
 
102
102
  @Test
103
- func detectsWorkspaceFromFreeTierWithHostedDomain() async throws {
103
+ func `detects workspace from free tier with hosted domain`() async throws {
104
104
  let env = try GeminiTestEnvironment()
105
105
  defer { env.cleanup() }
106
106
  let idToken = GeminiAPITestHelpers.makeIDToken(email: "user@company.com", hostedDomain: "company.com")
@@ -145,7 +145,7 @@ struct GeminiStatusProbePlanTests {
145
145
  }
146
146
 
147
147
  @Test
148
- func detectsFreeFromFreeTierWithoutHostedDomain() async throws {
148
+ func `detects free from free tier without hosted domain`() async throws {
149
149
  let env = try GeminiTestEnvironment()
150
150
  defer { env.cleanup() }
151
151
  let idToken = GeminiAPITestHelpers.makeIDToken(email: "user@gmail.com")
@@ -190,7 +190,7 @@ struct GeminiStatusProbePlanTests {
190
190
  }
191
191
 
192
192
  @Test
193
- func detectsLegacyFromLegacyTier() async throws {
193
+ func `detects legacy from legacy tier`() async throws {
194
194
  let env = try GeminiTestEnvironment()
195
195
  defer { env.cleanup() }
196
196
  try env.writeCredentials(
@@ -234,7 +234,7 @@ struct GeminiStatusProbePlanTests {
234
234
  }
235
235
 
236
236
  @Test
237
- func leavesBlankWhenLoadCodeAssistFails() async throws {
237
+ func `leaves blank when load code assist fails`() async throws {
238
238
  let env = try GeminiTestEnvironment()
239
239
  defer { env.cleanup() }
240
240
  try env.writeCredentials(
@@ -1,10 +1,9 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
 
5
- @Suite
6
5
  struct GeminiStatusProbeTests {
7
- // Sample /stats output from Gemini CLI (actual format with box-drawing chars)
6
+ /// Sample /stats output from Gemini CLI (actual format with box-drawing chars)
8
7
  static let sampleStatsOutput = """
9
8
  │ Model Usage Reqs Usage left │
10
9
  │ ─────────────────────────────────────────────────────────────────────── │
@@ -17,14 +16,14 @@ struct GeminiStatusProbeTests {
17
16
  // MARK: - Legacy CLI parsing tests (kept for fallback support)
18
17
 
19
18
  @Test
20
- func parsesMinimumPercentFromMultipleModels() throws {
19
+ func `parses minimum percent from multiple models`() throws {
21
20
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
22
21
  #expect(snap.dailyPercentLeft == 99.8)
23
22
  #expect(snap.resetDescription == "Resets in 20h 37m")
24
23
  }
25
24
 
26
25
  @Test
27
- func parsesLowerPercentCorrectly() throws {
26
+ func `parses lower percent correctly`() throws {
28
27
  let output = """
29
28
  │ Model Usage Reqs Usage left │
30
29
  │ gemini-2.5-flash 10 85.5% (Resets in 12h) │
@@ -36,7 +35,7 @@ struct GeminiStatusProbeTests {
36
35
  }
37
36
 
38
37
  @Test
39
- func handlesZeroPercentUsage() throws {
38
+ func `handles zero percent usage`() throws {
40
39
  let output = """
41
40
  │ gemini-2.5-flash 50 0.0% (Resets in 6h) │
42
41
  │ gemini-2.5-pro 20 15.0% (Resets in 6h) │
@@ -47,7 +46,7 @@ struct GeminiStatusProbeTests {
47
46
  }
48
47
 
49
48
  @Test
50
- func handles100PercentRemaining() throws {
49
+ func `handles100 percent remaining`() throws {
51
50
  let output = """
52
51
  │ gemini-2.5-flash - 100.0% (Resets in 24h) │
53
52
  """
@@ -56,14 +55,14 @@ struct GeminiStatusProbeTests {
56
55
  }
57
56
 
58
57
  @Test
59
- func throwsOnEmptyOutput() {
58
+ func `throws on empty output`() {
60
59
  #expect(throws: GeminiStatusProbeError.self) {
61
60
  try GeminiStatusProbe.parse(text: "")
62
61
  }
63
62
  }
64
63
 
65
64
  @Test
66
- func throwsOnNoUsageData() {
65
+ func `throws on no usage data`() {
67
66
  let output = """
68
67
  Welcome to Gemini CLI!
69
68
  Type /help for available commands.
@@ -74,7 +73,7 @@ struct GeminiStatusProbeTests {
74
73
  }
75
74
 
76
75
  @Test
77
- func stripsANSICodesBeforeParsing() throws {
76
+ func `strips ANSI codes before parsing`() throws {
78
77
  // swiftlint:disable:next line_length
79
78
  let output = "\u{1B}[32m│\u{1B}[0m gemini-2.5-flash - 75.5% (Resets in 18h) │"
80
79
  let snap = try GeminiStatusProbe.parse(text: output)
@@ -82,7 +81,7 @@ struct GeminiStatusProbeTests {
82
81
  }
83
82
 
84
83
  @Test
85
- func preservesRawText() throws {
84
+ func `preserves raw text`() throws {
86
85
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
87
86
  #expect(snap.rawText == Self.sampleStatsOutput)
88
87
  #expect(snap.accountEmail == nil) // Legacy parse doesn't extract email
@@ -90,7 +89,7 @@ struct GeminiStatusProbeTests {
90
89
  }
91
90
 
92
91
  @Test
93
- func parsesVariousResetDescriptions() throws {
92
+ func `parses various reset descriptions`() throws {
94
93
  let cases: [(String, String)] = [
95
94
  ("Resets in 24h", "Resets in 24h"),
96
95
  ("Resets in 1h 30m", "Resets in 1h 30m"),
@@ -106,7 +105,7 @@ struct GeminiStatusProbeTests {
106
105
  }
107
106
 
108
107
  @Test
109
- func throwsNotLoggedInOnAuthPrompt() {
108
+ func `throws not logged in on auth prompt`() {
110
109
  let authOutputs = [
111
110
  "Waiting for auth... (Press ESC or CTRL+C to cancel)",
112
111
  "Login with Google\nUse Gemini API key",
@@ -122,7 +121,7 @@ struct GeminiStatusProbeTests {
122
121
  // MARK: - Model quota grouping tests
123
122
 
124
123
  @Test
125
- func parsesModelsIntoQuotaArray() throws {
124
+ func `parses models into quota array`() throws {
126
125
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
127
126
  // Should parse multiple models (exact count may change as Google adds/removes models)
128
127
  #expect(snap.modelQuotas.count >= 2)
@@ -134,14 +133,14 @@ struct GeminiStatusProbeTests {
134
133
  }
135
134
 
136
135
  @Test
137
- func lowestPercentLeftReturnsMinimum() throws {
136
+ func `lowest percent left returns minimum`() throws {
138
137
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
139
138
  // Flash models are 99.8%, Pro models are 100%, so min should be 99.8
140
139
  #expect(snap.lowestPercentLeft == 99.8)
141
140
  }
142
141
 
143
142
  @Test
144
- func tierGroupingByKeyword() throws {
143
+ func `tier grouping by keyword`() throws {
145
144
  // Test that flash/pro keyword filtering works (model names may change)
146
145
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
147
146
 
@@ -154,7 +153,7 @@ struct GeminiStatusProbeTests {
154
153
  }
155
154
 
156
155
  @Test
157
- func tierMinimumCalculation() throws {
156
+ func `tier minimum calculation`() throws {
158
157
  // Use controlled test data to verify min-per-tier logic
159
158
  // Model names must start with "gemini-" to match the parser regex
160
159
  let output = """
@@ -176,7 +175,7 @@ struct GeminiStatusProbeTests {
176
175
  }
177
176
 
178
177
  @Test
179
- func quotasHaveResetDescriptions() throws {
178
+ func `quotas have reset descriptions`() throws {
180
179
  let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
181
180
 
182
181
  // At least some quotas should have reset descriptions
@@ -185,7 +184,7 @@ struct GeminiStatusProbeTests {
185
184
  }
186
185
 
187
186
  @Test
188
- func toUsageSnapshotPreservesModelLabelsAndTertiaryWindow() throws {
187
+ func `to usage snapshot preserves model labels and tertiary window`() throws {
189
188
  let output = """
190
189
  │ gemini-2.5-pro 2 40.0% (Resets in 6h) │
191
190
  │ gemini-2.5-flash 5 65.0% (Resets in 3h) │
@@ -202,7 +201,7 @@ struct GeminiStatusProbeTests {
202
201
  }
203
202
 
204
203
  @Test
205
- func toUsageSnapshotFallsBackWhenProTierIsMissing() throws {
204
+ func `to usage snapshot falls back when pro tier is missing`() throws {
206
205
  let output = """
207
206
  │ gemini-2.5-flash 2 90.0% (Resets in 6h) │
208
207
  │ gemini-2.5-flash-lite 3 25.0% (Resets in 6h) │
@@ -216,7 +215,7 @@ struct GeminiStatusProbeTests {
216
215
  // MARK: - Live API test
217
216
 
218
217
  @Test
219
- func liveGeminiFetch() async throws {
218
+ func `live gemini fetch`() async throws {
220
219
  guard ProcessInfo.processInfo.environment["LIVE_GEMINI_FETCH"] == "1" else {
221
220
  return
222
221
  }
@@ -2,13 +2,12 @@ import Foundation
2
2
  import Testing
3
3
  @testable import Runic
4
4
 
5
- @Suite
6
5
  @MainActor
7
6
  struct GoogleWorkspaceStatusTests {
8
7
  private let productID = "npdyhgECDJ6tB66MxXyo"
9
8
 
10
9
  @Test
11
- func parseWorkspaceStatusSelectsWorstIncident() throws {
10
+ func `parse workspace status selects worst incident`() throws {
12
11
  let data = Data(#"""
13
12
  [
14
13
  {
@@ -47,7 +46,7 @@ struct GoogleWorkspaceStatusTests {
47
46
  }
48
47
 
49
48
  @Test
50
- func parseWorkspaceStatusIgnoresResolvedIncidents() throws {
49
+ func `parse workspace status ignores resolved incidents`() throws {
51
50
  let data = Data(#"""
52
51
  [
53
52
  {
@@ -2,10 +2,9 @@ import Foundation
2
2
  import Testing
3
3
  @testable import Runic
4
4
 
5
- @Suite
6
5
  struct InstallOriginTests {
7
6
  @Test
8
- func detectsHomebrewCaskroom() {
7
+ func `detects homebrew caskroom`() {
9
8
  #expect(
10
9
  InstallOrigin
11
10
  .isHomebrewCask(
@@ -2,8 +2,8 @@ import KeyboardShortcuts
2
2
  import Testing
3
3
 
4
4
  @MainActor
5
- @Suite struct KeyboardShortcutsBundleTests {
6
- @Test func recorderInitializesWithoutCrashing() {
5
+ struct KeyboardShortcutsBundleTests {
6
+ @Test func `recorder initializes without crashing`() {
7
7
  _ = KeyboardShortcuts.RecorderCocoa(for: .init("test.keyboardshortcuts.bundle"))
8
8
  }
9
9
  }
@@ -1,13 +1,13 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
  import XCTest
5
5
  @testable import Runic
6
6
 
7
- @Suite("Live RPC account checks", .serialized)
7
+ @Suite(.serialized)
8
8
  struct LiveAccountTests {
9
9
  @Test(.disabled("Set LIVE_TEST=1 to run live Codex account checks."))
10
- func codexAccountEmailIsPresent() async throws {
10
+ func `codex account email is present`() async throws {
11
11
  guard ProcessInfo.processInfo.environment["LIVE_TEST"] == "1" else { return }
12
12
 
13
13
  let fetcher = UsageFetcher()