@sriinnu/runic 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +8 -1
  3. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  4. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  5. package/RuniciOS/Views/ProviderListView.swift +10 -10
  6. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  7. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  8. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  9. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  10. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  11. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  12. package/Sources/Runic/About.swift +3 -3
  13. package/Sources/Runic/AppNotifications.swift +5 -3
  14. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
  15. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  16. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  17. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  18. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +18 -13
  19. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  20. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  21. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  22. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  23. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  24. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  25. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  26. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  28. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  29. package/Sources/Runic/Core/RunicColors.swift +6 -6
  30. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  31. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  32. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +1 -3
  33. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
  34. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
  35. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
  36. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
  37. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  38. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
  39. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
  40. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
  41. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
  42. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
  43. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
  44. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
  45. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
  46. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
  47. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
  48. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  49. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
  50. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
  51. package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
  52. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
  53. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  54. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  55. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  56. package/Sources/Runic/Core/Stores/UsageStore.swift +187 -161
  57. package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
  58. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
  59. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  60. package/Sources/Runic/ProviderRegistry.swift +1 -1
  61. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  62. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  63. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  81. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  83. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  84. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  85. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  87. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  88. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  107. package/Sources/Runic/RunicApp.swift +46 -36
  108. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  109. package/Sources/Runic/UsagePaceText.swift +1 -1
  110. package/Sources/Runic/UsageProgressBar.swift +13 -9
  111. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  112. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  113. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  114. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  115. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  116. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  117. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  118. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  119. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  120. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +12 -10
  121. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  122. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  123. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  124. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  125. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  126. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  127. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  128. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  129. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +18 -15
  130. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  131. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  132. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  133. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  134. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  135. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  136. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +22 -17
  137. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  138. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  139. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  140. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  141. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  142. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  143. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  144. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  145. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  146. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  147. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  148. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  149. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  150. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  151. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +44 -39
  152. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  153. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  154. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  155. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  156. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  157. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  158. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  159. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  160. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  161. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  162. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  163. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  164. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  165. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  166. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  167. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  168. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  169. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  170. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  171. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  172. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  173. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  174. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +73 -75
  175. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  176. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  177. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  178. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  179. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  180. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  181. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  182. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  183. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  184. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  185. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  186. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  187. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  188. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  189. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  190. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +3 -3
  191. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  192. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  193. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  194. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  195. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  196. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  197. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  198. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  199. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  200. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +1 -1
  201. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  202. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  204. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  205. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  206. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  207. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  208. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  209. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  210. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  211. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  212. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  213. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  214. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  215. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +2 -2
  216. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  217. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  218. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  219. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +5 -2
  220. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  221. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  222. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  225. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +7 -7
  226. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  227. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  228. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  229. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  230. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +23 -10
  231. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  232. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  233. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  234. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  235. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  236. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +26 -13
  237. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  240. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  241. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  242. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  243. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  244. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  245. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  246. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  247. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +48 -21
  248. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  249. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  250. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  251. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  252. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  253. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  254. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  255. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  256. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  257. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  258. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  259. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +27 -13
  260. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
  261. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
  262. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  263. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  264. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  265. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  266. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  267. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  268. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  269. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  270. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  271. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  272. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  273. package/Tests/RunicTests/CLICostTests.swift +6 -7
  274. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  275. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  276. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  277. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  278. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  279. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  280. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  281. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  282. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  283. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  284. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  285. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  286. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  287. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  288. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  289. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  290. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  291. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  292. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  293. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  294. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  295. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  296. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  297. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  298. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  299. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  300. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  301. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  302. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  303. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  304. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  305. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  306. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  307. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  308. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  309. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  310. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  311. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  312. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  313. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  314. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  315. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  316. package/Tests/RunicTests/RunicTests.swift +6 -7
  317. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  318. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  319. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  320. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  321. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  322. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  323. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  324. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  325. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  326. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  327. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  328. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  329. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  330. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  331. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  332. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  333. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  334. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  335. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  336. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  337. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  338. package/appcast.xml +36 -76
  339. package/package.json +1 -1
  340. package/runic.png +0 -0
  341. package/runic.svg +15 -0
  342. package/version.env +2 -2
@@ -1,12 +1,11 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
  @testable import Runic
5
5
 
6
- @Suite
7
6
  struct StatusProbeTests {
8
7
  @Test
9
- func parseCodexStatus() throws {
8
+ func `parse codex status`() throws {
10
9
  let sample = """
11
10
  Model: gpt
12
11
  Credits: 980 credits
@@ -20,7 +19,7 @@ struct StatusProbeTests {
20
19
  }
21
20
 
22
21
  @Test
23
- func parseCodexStatusWithAnsiAndResets() throws {
22
+ func `parse codex status with ansi and resets`() throws {
24
23
  let sample = """
25
24
  \u{001B}[38;5;245mCredits:\u{001B}[0m 557 credits
26
25
  5h limit: [█████ ] 50% left (resets 09:01)
@@ -33,7 +32,7 @@ struct StatusProbeTests {
33
32
  }
34
33
 
35
34
  @Test
36
- func parseClaudeStatus() throws {
35
+ func `parse claude status`() throws {
37
36
  let sample = """
38
37
  Settings: Status Config Usage (tab to cycle)
39
38
 
@@ -64,7 +63,7 @@ struct StatusProbeTests {
64
63
  }
65
64
 
66
65
  @Test
67
- func parseClaudeStatusWithANSI() throws {
66
+ func `parse claude status with ANSI`() throws {
68
67
  let sample = """
69
68
  \u{001B}[35mCurrent session\u{001B}[0m
70
69
  40% used (Resets 11am)
@@ -86,7 +85,7 @@ struct StatusProbeTests {
86
85
  }
87
86
 
88
87
  @Test
89
- func parseClaudeStatusLegacyOpusLabel() throws {
88
+ func `parse claude status legacy opus label`() throws {
90
89
  let sample = """
91
90
  Current session
92
91
  12% used (Resets 11am)
@@ -107,7 +106,7 @@ struct StatusProbeTests {
107
106
  }
108
107
 
109
108
  @Test
110
- func parseClaudeStatusEnterpriseSessionOnly() throws {
109
+ func `parse claude status enterprise session only`() throws {
111
110
  let sample = """
112
111
  Current session
113
112
  █ 2% used
@@ -121,7 +120,7 @@ struct StatusProbeTests {
121
120
  }
122
121
 
123
122
  @Test
124
- func parseClaudeStatusResetMappings_withCRLineEndings() throws {
123
+ func `parse claude status reset mappings with CR line endings`() throws {
125
124
  let sample =
126
125
  "Current session\r" +
127
126
  "██████████████████████████████████████████████████ 17% used\r" +
@@ -143,7 +142,7 @@ struct StatusProbeTests {
143
142
  }
144
143
 
145
144
  @Test
146
- func parseClaudeStatusResetMappings_doesNotPromoteWeeklyResetToSession() throws {
145
+ func `parse claude status reset mappings does not promote weekly reset to session`() throws {
147
146
  let sample = """
148
147
  Current session
149
148
  ██████████████████████████████████████████████████ 17% used
@@ -159,7 +158,7 @@ struct StatusProbeTests {
159
158
  }
160
159
 
161
160
  @Test
162
- func parseClaudeStatusWithPlanAndAnsiNoise() throws {
161
+ func `parse claude status with plan and ansi noise`() throws {
163
162
  let sample = """
164
163
  Settings: Status Config Usage
165
164
 
@@ -184,7 +183,7 @@ struct StatusProbeTests {
184
183
  }
185
184
 
186
185
  @Test
187
- func parseClaudeStatusWithBracketPlanNoiseNoEsc() throws {
186
+ func `parse claude status with bracket plan noise no esc`() throws {
188
187
  let sample = """
189
188
  Login method: [22m Claude Max Account
190
189
  Account: user@example.com
@@ -203,7 +202,7 @@ struct StatusProbeTests {
203
202
  }
204
203
 
205
204
  @Test
206
- func surfacesClaudeTokenExpired() {
205
+ func `surfaces claude token expired`() {
207
206
  let sample = """
208
207
  Settings: Status Config Usage
209
208
 
@@ -226,7 +225,7 @@ struct StatusProbeTests {
226
225
  }
227
226
 
228
227
  @Test
229
- func surfacesClaudeFolderTrustPrompt() {
228
+ func `surfaces claude folder trust prompt`() {
230
229
  let sample = """
231
230
  Do you trust the files in this folder?
232
231
 
@@ -246,7 +245,7 @@ struct StatusProbeTests {
246
245
  }
247
246
 
248
247
  @Test
249
- func surfacesClaudeFolderTrustPrompt_withCRLFAndSpaces() {
248
+ func `surfaces claude folder trust prompt with CRLF and spaces`() {
250
249
  let sample = "Do you trust the files in this folder?\r\n\r\n/Users/example/My Project\r\n"
251
250
 
252
251
  do {
@@ -261,7 +260,7 @@ struct StatusProbeTests {
261
260
  }
262
261
 
263
262
  @Test
264
- func surfacesClaudeFolderTrustPrompt_withoutFolderPath() {
263
+ func `surfaces claude folder trust prompt without folder path`() {
265
264
  let sample = """
266
265
  Do you trust the files in this folder?
267
266
  """
@@ -279,25 +278,25 @@ struct StatusProbeTests {
279
278
  }
280
279
 
281
280
  @Test
282
- func parsesClaudeResetTimeOnly() {
281
+ func `parses claude reset time only`() throws {
283
282
  let now = Date(timeIntervalSince1970: 1_733_690_000)
284
283
  let parsed = ClaudeStatusProbe.parseResetDate(from: "Resets 12:59pm (Europe/Helsinki)", now: now)
285
- let tz = TimeZone(identifier: "Europe/Helsinki")!
284
+ let tz = try #require(TimeZone(identifier: "Europe/Helsinki"))
286
285
  var calendar = Calendar(identifier: .gregorian)
287
286
  calendar.timeZone = tz
288
- var expected = calendar.date(bySettingHour: 12, minute: 59, second: 0, of: now)!
287
+ var expected = try #require(calendar.date(bySettingHour: 12, minute: 59, second: 0, of: now))
289
288
  if expected < now {
290
- expected = calendar.date(byAdding: .day, value: 1, to: expected)!
289
+ expected = try #require(calendar.date(byAdding: .day, value: 1, to: expected))
291
290
  }
292
291
  #expect(parsed == expected)
293
292
  }
294
293
 
295
294
  @Test
296
- func parsesClaudeResetDateAndTime() {
295
+ func `parses claude reset date and time`() throws {
297
296
  let now = Date(timeIntervalSince1970: 1_733_690_000)
298
297
  let parsed = ClaudeStatusProbe.parseResetDate(from: "Resets Dec 9, 8:59am (Europe/Helsinki)", now: now)
299
298
  var calendar = Calendar(identifier: .gregorian)
300
- calendar.timeZone = TimeZone(identifier: "Europe/Helsinki")!
299
+ calendar.timeZone = try #require(TimeZone(identifier: "Europe/Helsinki"))
301
300
  let expected = calendar.date(from: DateComponents(
302
301
  year: calendar.component(.year, from: now),
303
302
  month: 12,
@@ -309,24 +308,24 @@ struct StatusProbeTests {
309
308
  }
310
309
 
311
310
  @Test
312
- func parsesClaudeResetWithDotSeparatedTime() {
311
+ func `parses claude reset with dot separated time`() throws {
313
312
  let now = Date(timeIntervalSince1970: 1_733_690_000)
314
313
  let parsed = ClaudeStatusProbe.parseResetDate(from: "Resets Dec 9 at 5.27am (UTC)", now: now)
315
314
  var calendar = Calendar(identifier: .gregorian)
316
- calendar.timeZone = TimeZone(identifier: "UTC")!
315
+ calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
317
316
  let expected = calendar.date(from: DateComponents(year: 2024, month: 12, day: 9, hour: 5, minute: 27))
318
317
  #expect(parsed == expected)
319
318
  }
320
319
 
321
320
  @Test
322
- func parsesClaudeResetWithCompactTimes() {
321
+ func `parses claude reset with compact times`() throws {
323
322
  let now = Date(timeIntervalSince1970: 1_733_690_000)
324
323
  let parsedTimeOnly = ClaudeStatusProbe.parseResetDate(from: "Resets 1pm (UTC)", now: now)
325
324
  var calendar = Calendar(identifier: .gregorian)
326
- calendar.timeZone = TimeZone(identifier: "UTC")!
327
- var expected = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: now)!
325
+ calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
326
+ var expected = try #require(calendar.date(bySettingHour: 13, minute: 0, second: 0, of: now))
328
327
  if expected < now {
329
- expected = calendar.date(byAdding: .day, value: 1, to: expected)!
328
+ expected = try #require(calendar.date(byAdding: .day, value: 1, to: expected))
330
329
  }
331
330
  #expect(parsedTimeOnly == expected)
332
331
 
@@ -343,7 +342,7 @@ struct StatusProbeTests {
343
342
  }
344
343
 
345
344
  @Test
346
- func liveCodexStatus() async throws {
345
+ func `live codex status`() async throws {
347
346
  guard ProcessInfo.processInfo.environment["LIVE_CODEX_STATUS"] == "1" else { return }
348
347
 
349
348
  let probe = CodexStatusProbe()
@@ -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 SubprocessRunnerTests {
8
6
  @Test
9
- func readsLargeStdoutWithoutDeadlock() async throws {
7
+ func `reads large stdout without deadlock`() async throws {
10
8
  let result = try await SubprocessRunner.run(
11
9
  binary: "/usr/bin/python3",
12
10
  arguments: ["-c", "print('x' * 1_000_000)"],
@@ -2,12 +2,11 @@ import Foundation
2
2
  import Testing
3
3
  @testable import Runic
4
4
 
5
- @Suite
6
5
  struct SubscriptionDetectionTests {
7
6
  // MARK: - Subscription plans should be detected
8
7
 
9
8
  @Test
10
- func detectsMaxPlan() {
9
+ func `detects max plan`() {
11
10
  #expect(UsageStore.isSubscriptionPlan("Claude Max") == true)
12
11
  #expect(UsageStore.isSubscriptionPlan("Max") == true)
13
12
  #expect(UsageStore.isSubscriptionPlan("claude max") == true)
@@ -15,21 +14,21 @@ struct SubscriptionDetectionTests {
15
14
  }
16
15
 
17
16
  @Test
18
- func detectsProPlan() {
17
+ func `detects pro plan`() {
19
18
  #expect(UsageStore.isSubscriptionPlan("Claude Pro") == true)
20
19
  #expect(UsageStore.isSubscriptionPlan("Pro") == true)
21
20
  #expect(UsageStore.isSubscriptionPlan("pro") == true)
22
21
  }
23
22
 
24
23
  @Test
25
- func detectsUltraPlan() {
24
+ func `detects ultra plan`() {
26
25
  #expect(UsageStore.isSubscriptionPlan("Claude Ultra") == true)
27
26
  #expect(UsageStore.isSubscriptionPlan("Ultra") == true)
28
27
  #expect(UsageStore.isSubscriptionPlan("ultra") == true)
29
28
  }
30
29
 
31
30
  @Test
32
- func detectsTeamPlan() {
31
+ func `detects team plan`() {
33
32
  #expect(UsageStore.isSubscriptionPlan("Claude Team") == true)
34
33
  #expect(UsageStore.isSubscriptionPlan("Team") == true)
35
34
  #expect(UsageStore.isSubscriptionPlan("team") == true)
@@ -38,18 +37,18 @@ struct SubscriptionDetectionTests {
38
37
  // MARK: - Non-subscription plans should return false
39
38
 
40
39
  @Test
41
- func nilLoginMethodReturnsFalse() {
40
+ func `nil login method returns false`() {
42
41
  #expect(UsageStore.isSubscriptionPlan(nil) == false)
43
42
  }
44
43
 
45
44
  @Test
46
- func emptyLoginMethodReturnsFalse() {
45
+ func `empty login method returns false`() {
47
46
  #expect(UsageStore.isSubscriptionPlan("") == false)
48
47
  #expect(UsageStore.isSubscriptionPlan(" ") == false)
49
48
  }
50
49
 
51
50
  @Test
52
- func unknownPlanReturnsFalse() {
51
+ func `unknown plan returns false`() {
53
52
  #expect(UsageStore.isSubscriptionPlan("API") == false)
54
53
  #expect(UsageStore.isSubscriptionPlan("Free") == false)
55
54
  #expect(UsageStore.isSubscriptionPlan("Unknown") == false)
@@ -57,7 +56,7 @@ struct SubscriptionDetectionTests {
57
56
  }
58
57
 
59
58
  @Test
60
- func apiKeyUsersReturnFalse() {
59
+ func `api key users return false`() {
61
60
  // API users typically don't have a login method or have non-subscription identifiers
62
61
  #expect(UsageStore.isSubscriptionPlan("api_key") == false)
63
62
  #expect(UsageStore.isSubscriptionPlan("console") == false)
@@ -2,10 +2,9 @@ import Foundation
2
2
  import Testing
3
3
  @testable import RunicCore
4
4
 
5
- @Suite
6
5
  struct TTYCommandRunnerEnvTests {
7
6
  @Test
8
- func preservesEnvironmentAndSetsTerm() {
7
+ func `preserves environment and sets term`() {
9
8
  let baseEnv: [String: String] = [
10
9
  "PATH": "/custom/bin",
11
10
  "HOME": "/Users/tester",
@@ -25,7 +24,7 @@ struct TTYCommandRunnerEnvTests {
25
24
  }
26
25
 
27
26
  @Test
28
- func backfillsHomeWhenMissing() {
27
+ func `backfills home when missing`() {
29
28
  let merged = TTYCommandRunner.enrichedEnvironment(
30
29
  baseEnv: ["PATH": "/custom/bin"],
31
30
  loginPATH: nil,
@@ -35,7 +34,7 @@ struct TTYCommandRunnerEnvTests {
35
34
  }
36
35
 
37
36
  @Test
38
- func preservesExistingTermAndCustomVars() {
37
+ func `preserves existing term and custom vars`() {
39
38
  let merged = TTYCommandRunner.enrichedEnvironment(
40
39
  baseEnv: [
41
40
  "PATH": "/custom/bin",
@@ -53,7 +52,7 @@ struct TTYCommandRunnerEnvTests {
53
52
  }
54
53
 
55
54
  @Test
56
- func setsWorkingDirectoryWhenProvided() throws {
55
+ func `sets working directory when provided`() throws {
57
56
  let fm = FileManager.default
58
57
  let dir = fm.temporaryDirectory.appendingPathComponent("runic-tty-\(UUID().uuidString)", isDirectory: true)
59
58
  try fm.createDirectory(at: dir, withIntermediateDirectories: true)
@@ -65,7 +64,7 @@ struct TTYCommandRunnerEnvTests {
65
64
  }
66
65
 
67
66
  @Test
68
- func autoRespondsToTrustPrompt() throws {
67
+ func `auto responds to trust prompt`() throws {
69
68
  let fm = FileManager.default
70
69
  let dir = fm.temporaryDirectory.appendingPathComponent("runic-tty-\(UUID().uuidString)", isDirectory: true)
71
70
  try fm.createDirectory(at: dir, withIntermediateDirectories: true)
@@ -102,7 +101,7 @@ struct TTYCommandRunnerEnvTests {
102
101
  }
103
102
 
104
103
  @Test
105
- func stopsWhenOutputIsIdle() throws {
104
+ func `stops when output is idle`() throws {
106
105
  let fm = FileManager.default
107
106
  let dir = fm.temporaryDirectory.appendingPathComponent("runic-tty-\(UUID().uuidString)", isDirectory: true)
108
107
  try fm.createDirectory(at: dir, withIntermediateDirectories: true)
@@ -130,7 +129,7 @@ struct TTYCommandRunnerEnvTests {
130
129
  }
131
130
 
132
131
  @Test
133
- func rollingBufferDetectsNeedleAcrossBoundary() {
132
+ func `rolling buffer detects needle across boundary`() {
134
133
  var scanner = TTYCommandRunner.RollingBuffer(maxNeedle: 6)
135
134
  let needle = Data("hello".utf8)
136
135
  let first = scanner.append(Data("he".utf8))
@@ -140,7 +139,7 @@ struct TTYCommandRunnerEnvTests {
140
139
  }
141
140
 
142
141
  @Test
143
- func lowercasedASCIIOnlyTouchesAscii() {
142
+ func `lowercased ASCII only touches ascii`() {
144
143
  let data = Data("UpDaTe".utf8)
145
144
  let lowered = TTYCommandRunner.lowercasedASCII(data)
146
145
  #expect(String(data: lowered, encoding: .utf8) == "update")
@@ -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 TeamShowbackExporterTests {
8
6
  @Test
9
- func buildsShowbackReportForTeamOwnedProjects() throws {
7
+ func `builds showback report for team owned projects`() throws {
10
8
  let period = DateInterval(
11
9
  start: self.date(year: 2026, month: 2, day: 1),
12
10
  end: self.date(year: 2026, month: 3, day: 1))
@@ -81,7 +79,7 @@ struct TeamShowbackExporterTests {
81
79
  }
82
80
 
83
81
  @Test
84
- func exportsJsonAndCsvFromReport() throws {
82
+ func `exports json and csv from report`() throws {
85
83
  let period = DateInterval(
86
84
  start: self.date(year: 2026, month: 2, day: 1),
87
85
  end: self.date(year: 2026, month: 3, day: 1))
@@ -1,10 +1,9 @@
1
1
  import RunicCore
2
2
  import Testing
3
3
 
4
- @Suite
5
4
  struct TextParsingTests {
6
5
  @Test
7
- func stripANSICodesRemovesCursorVisibilityCSI() {
6
+ func `strip ANSI codes removes cursor visibility CSI`() {
8
7
  let input = "\u{001B}[?25hhello\u{001B}[0m"
9
8
  let stripped = TextParsing.stripANSICodes(input)
10
9
  #expect(stripped == "hello")
@@ -1,18 +1,17 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
  import Testing
4
4
  @testable import Runic
5
5
 
6
- @Suite
7
6
  struct UsageFormatterTests {
8
7
  @Test
9
- func formatsUsageLine() {
8
+ func `formats usage line`() {
10
9
  let line = UsageFormatter.usageLine(remaining: 25, used: 75)
11
10
  #expect(line == "25% left")
12
11
  }
13
12
 
14
13
  @Test
15
- func relativeUpdatedRecent() {
14
+ func `relative updated recent`() {
16
15
  let now = Date()
17
16
  let fiveHoursAgo = now.addingTimeInterval(-5 * 3600)
18
17
  let text = UsageFormatter.updatedString(from: fiveHoursAgo, now: now)
@@ -22,7 +21,7 @@ struct UsageFormatterTests {
22
21
  }
23
22
 
24
23
  @Test
25
- func absoluteUpdatedOld() {
24
+ func `absolute updated old`() {
26
25
  let now = Date()
27
26
  let dayAgo = now.addingTimeInterval(-26 * 3600)
28
27
  let text = UsageFormatter.updatedString(from: dayAgo, now: now)
@@ -31,42 +30,42 @@ struct UsageFormatterTests {
31
30
  }
32
31
 
33
32
  @Test
34
- func resetCountdown_minutes() {
33
+ func `reset countdown minutes`() {
35
34
  let now = Date(timeIntervalSince1970: 1_000_000)
36
35
  let reset = now.addingTimeInterval(10 * 60 + 1)
37
36
  #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == "in 11m")
38
37
  }
39
38
 
40
39
  @Test
41
- func resetCountdown_hoursAndMinutes() {
40
+ func `reset countdown hours and minutes`() {
42
41
  let now = Date(timeIntervalSince1970: 1_000_000)
43
42
  let reset = now.addingTimeInterval(3 * 3600 + 31 * 60)
44
43
  #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == "in 3h 31m")
45
44
  }
46
45
 
47
46
  @Test
48
- func resetCountdown_daysAndHours() {
47
+ func `reset countdown days and hours`() {
49
48
  let now = Date(timeIntervalSince1970: 1_000_000)
50
49
  let reset = now.addingTimeInterval((26 * 3600) + 10)
51
50
  #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == "in 1d 2h")
52
51
  }
53
52
 
54
53
  @Test
55
- func resetCountdown_exactHour() {
54
+ func `reset countdown exact hour`() {
56
55
  let now = Date(timeIntervalSince1970: 1_000_000)
57
56
  let reset = now.addingTimeInterval(60 * 60)
58
57
  #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == "in 1h")
59
58
  }
60
59
 
61
60
  @Test
62
- func resetCountdown_pastDate() {
61
+ func `reset countdown past date`() {
63
62
  let now = Date(timeIntervalSince1970: 1_000_000)
64
63
  let reset = now.addingTimeInterval(-10)
65
64
  #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == "now")
66
65
  }
67
66
 
68
67
  @Test
69
- func modelDisplayNameStripsTrailingDates() {
68
+ func `model display name strips trailing dates`() {
70
69
  #expect(UsageFormatter.modelDisplayName("claude-opus-4-5-20251101") == "claude-opus-4-5")
71
70
  #expect(UsageFormatter.modelDisplayName("gpt-4o-2024-08-06") == "gpt-4o")
72
71
  #expect(UsageFormatter.modelDisplayName("Claude Opus 4.5 2025 1101") == "Claude Opus 4.5")
@@ -74,12 +73,12 @@ struct UsageFormatterTests {
74
73
  }
75
74
 
76
75
  @Test
77
- func cleanPlanMapsOAuthToOllama() {
76
+ func `clean plan maps O auth to ollama`() {
78
77
  #expect(UsageFormatter.cleanPlanName("oauth") == "Ollama")
79
78
  }
80
79
 
81
80
  @Test
82
- func modelContextFromKnownModel() {
81
+ func `model context from known model`() {
83
82
  #expect(UsageFormatter.modelContextLabel(for: "gpt-4o") == "ctx 128K")
84
83
  #expect(UsageFormatter.modelContextLabel(for: "claude-opus-4-5") == "ctx 200K")
85
84
  #expect(UsageFormatter.modelContextLabel(for: "gpt-5") == "ctx 400K")
@@ -89,18 +88,18 @@ struct UsageFormatterTests {
89
88
  }
90
89
 
91
90
  @Test
92
- func modelContextFromNameSuffix() {
91
+ func `model context from name suffix`() {
93
92
  #expect(UsageFormatter.modelContextWindow(for: "qwen2.5-128k-instruct") == 128_000)
94
93
  #expect(UsageFormatter.modelContextLabel(for: "provider/qwen2.5-2m-long-context") == "ctx 2M")
95
94
  }
96
95
 
97
96
  @Test
98
- func modelContextUnknownReturnsNil() {
97
+ func `model context unknown returns nil`() {
99
98
  #expect(UsageFormatter.modelContextWindow(for: "unknown-custom-model-v1") == nil)
100
99
  }
101
100
 
102
101
  @Test
103
- func tokenSummaryIncludesBreakdown() {
102
+ func `token summary includes breakdown`() {
104
103
  let totals = UsageLedgerTotals(
105
104
  inputTokens: 1000,
106
105
  outputTokens: 200,
@@ -1,13 +1,11 @@
1
1
  import Foundation
2
2
  import RunicCore
3
3
  import Testing
4
-
5
4
  @testable import Runic
6
5
 
7
- @Suite
8
6
  struct UsageLedgerAnomalyDetectionTests {
9
7
  @Test
10
- func detectsTokenSpikeAgainstSevenDayBaseline() {
8
+ func `detects token spike against seven day baseline`() {
11
9
  let now = Date(timeIntervalSince1970: 1_771_718_400) // 2026-02-22T00:00:00Z
12
10
  var calendar = Calendar(identifier: .gregorian)
13
11
  calendar.timeZone = TimeZone.current
@@ -17,7 +15,7 @@ struct UsageLedgerAnomalyDetectionTests {
17
15
  summaries.append(self.dailySummary(
18
16
  provider: .codex,
19
17
  dayOffset: offset,
20
- tokens: 1_000,
18
+ tokens: 1000,
21
19
  cost: nil,
22
20
  now: now,
23
21
  calendar: calendar))
@@ -25,7 +23,7 @@ struct UsageLedgerAnomalyDetectionTests {
25
23
  summaries.append(self.dailySummary(
26
24
  provider: .codex,
27
25
  dayOffset: 0,
28
- tokens: 2_500,
26
+ tokens: 2500,
29
27
  cost: nil,
30
28
  now: now,
31
29
  calendar: calendar))
@@ -43,7 +41,7 @@ struct UsageLedgerAnomalyDetectionTests {
43
41
  }
44
42
 
45
43
  @Test
46
- func detectsSpendSpikeWhenCostDataExists() {
44
+ func `detects spend spike when cost data exists`() {
47
45
  let now = Date(timeIntervalSince1970: 1_771_718_400) // 2026-02-22T00:00:00Z
48
46
  var calendar = Calendar(identifier: .gregorian)
49
47
  calendar.timeZone = TimeZone.current
@@ -53,7 +51,7 @@ struct UsageLedgerAnomalyDetectionTests {
53
51
  summaries.append(self.dailySummary(
54
52
  provider: .claude,
55
53
  dayOffset: offset,
56
- tokens: 1_000,
54
+ tokens: 1000,
57
55
  cost: 2.0,
58
56
  now: now,
59
57
  calendar: calendar))
@@ -61,7 +59,7 @@ struct UsageLedgerAnomalyDetectionTests {
61
59
  summaries.append(self.dailySummary(
62
60
  provider: .claude,
63
61
  dayOffset: 0,
64
- tokens: 1_100,
62
+ tokens: 1100,
65
63
  cost: 7.0,
66
64
  now: now,
67
65
  calendar: calendar))
@@ -79,7 +77,7 @@ struct UsageLedgerAnomalyDetectionTests {
79
77
  }
80
78
 
81
79
  @Test
82
- func doesNotEmitAnomalyWhenHistoryHasFewerThanSevenDays() {
80
+ func `does not emit anomaly when history has fewer than seven days`() {
83
81
  let now = Date(timeIntervalSince1970: 1_771_718_400) // 2026-02-22T00:00:00Z
84
82
  var calendar = Calendar(identifier: .gregorian)
85
83
  calendar.timeZone = TimeZone.current
@@ -97,7 +95,7 @@ struct UsageLedgerAnomalyDetectionTests {
97
95
  summaries.append(self.dailySummary(
98
96
  provider: .codex,
99
97
  dayOffset: 0,
100
- tokens: 4_000,
98
+ tokens: 4000,
101
99
  cost: 8.0,
102
100
  now: now,
103
101
  calendar: calendar))
@@ -112,15 +110,15 @@ struct UsageLedgerAnomalyDetectionTests {
112
110
  }
113
111
 
114
112
  @Test
115
- func explanationIncludesHeadlineAndContributingFactors() {
113
+ func `explanation includes headline and contributing factors`() {
116
114
  let summary = UsageLedgerAnomalySummary(
117
115
  provider: .codex,
118
116
  baselineDays: 7,
119
117
  tokenAnomaly: UsageLedgerAnomalySummary.MetricAnomaly(
120
118
  metric: .tokens,
121
119
  severity: .high,
122
- todayValue: 4_200,
123
- baselineAverage: 1_500,
120
+ todayValue: 4200,
121
+ baselineAverage: 1500,
124
122
  percentIncrease: 1.8),
125
123
  spendAnomaly: UsageLedgerAnomalySummary.MetricAnomaly(
126
124
  metric: .spend,
@@ -137,7 +135,7 @@ struct UsageLedgerAnomalyDetectionTests {
137
135
  }
138
136
 
139
137
  @Test
140
- func explanationIsNilWhenNoAnomaliesExist() {
138
+ func `explanation is nil when no anomalies exist`() {
141
139
  let summary = UsageLedgerAnomalySummary(
142
140
  provider: .codex,
143
141
  baselineDays: 7,
@@ -1,14 +1,12 @@
1
1
  import Foundation
2
2
  import Testing
3
-
4
3
  @testable import RunicCore
5
4
 
6
- @Suite
7
5
  struct UsageLedgerForecastTests {
8
6
  private let utc = TimeZone(secondsFromGMT: 0)!
9
7
 
10
8
  @Test
11
- func providerForecastUsesObservedDaysInCurrentMonth() {
9
+ func `provider forecast uses observed days in current month`() {
12
10
  let now = self.date(year: 2026, month: 2, day: 20)
13
11
  let entries = [
14
12
  self.entry(
@@ -50,7 +48,7 @@ struct UsageLedgerForecastTests {
50
48
  }
51
49
 
52
50
  @Test
53
- func projectForecastsAreComputedPerProject() {
51
+ func `project forecasts are computed per project`() {
54
52
  let now = self.date(year: 2026, month: 2, day: 20)
55
53
  let entries = [
56
54
  self.entry(
@@ -91,7 +89,7 @@ struct UsageLedgerForecastTests {
91
89
  }
92
90
 
93
91
  @Test
94
- func providerForecastIncludesConfidenceBandQuantilesWhenEnoughDailyHistoryExists() {
92
+ func `provider forecast includes confidence band quantiles when enough daily history exists`() {
95
93
  let now = self.date(year: 2026, month: 2, day: 20)
96
94
  let entries = [
97
95
  self.entry(
@@ -142,7 +140,7 @@ struct UsageLedgerForecastTests {
142
140
  }
143
141
 
144
142
  @Test
145
- func budgetETAIsComputedOnlyWhenForecastBreachesBudget() {
143
+ func `budget ETA is computed only when forecast breaches budget`() {
146
144
  let base = UsageLedgerSpendForecast(
147
145
  provider: .codex,
148
146
  projectKey: "id:proj-a",