@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
@@ -18,7 +18,7 @@ struct TeamManagementView: View {
18
18
  @State private var editingMember: TeamMember?
19
19
  @State private var editingMemberTeamID: String?
20
20
  @State private var memberQuotaHasLimit = false
21
- @State private var memberQuotaLimit = 10_000
21
+ @State private var memberQuotaLimit = 10000
22
22
  @State private var changingRoleMember: TeamMember?
23
23
  @State private var changingRoleTeamID: String?
24
24
  @State private var memberRoleSelection: TeamRole = .member
@@ -30,7 +30,7 @@ struct TeamManagementView: View {
30
30
  PreferencesPane {
31
31
  SettingsSection(title: "Teams", contentSpacing: RunicSpacing.md) {
32
32
  Text("Manage team workspaces, members, and quota allocation.")
33
- .font(.footnote)
33
+ .font(RunicFont.footnote)
34
34
  .foregroundStyle(.tertiary)
35
35
  .fixedSize(horizontal: false, vertical: true)
36
36
  }
@@ -41,7 +41,7 @@ struct TeamManagementView: View {
41
41
  VStack(alignment: .leading, spacing: RunicSpacing.sm) {
42
42
  HStack {
43
43
  Text("Your Teams")
44
- .font(.subheadline.weight(.semibold))
44
+ .font(RunicFont.subheadline.weight(.semibold))
45
45
  Spacer()
46
46
  Button {
47
47
  self.showingCreateTeam = true
@@ -58,10 +58,10 @@ struct TeamManagementView: View {
58
58
  .font(.system(size: 48))
59
59
  .foregroundStyle(.tertiary)
60
60
  Text("No teams yet")
61
- .font(.body.weight(.semibold))
61
+ .font(RunicFont.body.weight(.semibold))
62
62
  .foregroundStyle(.secondary)
63
63
  Text("Create a team to collaborate with others")
64
- .font(.footnote)
64
+ .font(RunicFont.footnote)
65
65
  .foregroundStyle(.tertiary)
66
66
  }
67
67
  .frame(maxWidth: .infinity)
@@ -103,7 +103,7 @@ struct TeamManagementView: View {
103
103
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
104
104
  HStack {
105
105
  Text("Members")
106
- .font(.footnote.weight(.semibold))
106
+ .font(RunicFont.footnote.weight(.semibold))
107
107
  .foregroundStyle(.secondary)
108
108
  Spacer()
109
109
  Button {
@@ -127,24 +127,24 @@ struct TeamManagementView: View {
127
127
 
128
128
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
129
129
  Text("Team Usage")
130
- .font(.footnote.weight(.semibold))
130
+ .font(RunicFont.footnote.weight(.semibold))
131
131
  .foregroundStyle(.secondary)
132
132
 
133
133
  HStack {
134
134
  Text("Total quota")
135
- .font(.footnote)
135
+ .font(RunicFont.footnote)
136
136
  Spacer()
137
137
  Text("\(team.totalQuota, format: .number) credits")
138
- .font(.footnote.weight(.semibold))
138
+ .font(RunicFont.footnote.weight(.semibold))
139
139
  .foregroundStyle(.secondary)
140
140
  }
141
141
 
142
142
  HStack {
143
143
  Text("Used")
144
- .font(.footnote)
144
+ .font(RunicFont.footnote)
145
145
  Spacer()
146
146
  Text("\(team.usedQuota, format: .number) credits")
147
- .font(.footnote.weight(.semibold))
147
+ .font(RunicFont.footnote.weight(.semibold))
148
148
  .foregroundStyle(.secondary)
149
149
  }
150
150
 
@@ -170,6 +170,7 @@ struct TeamManagementView: View {
170
170
  }
171
171
  }
172
172
  }
173
+ .runicTypography()
173
174
  .sheet(isPresented: self.$showingCreateTeam) {
174
175
  CreateTeamSheet(
175
176
  teamName: self.$newTeamName,
@@ -323,7 +324,7 @@ struct TeamManagementView: View {
323
324
  self.editingMember = member
324
325
  self.editingMemberTeamID = team.id
325
326
  self.memberQuotaHasLimit = member.quotaLimit != nil
326
- self.memberQuotaLimit = member.quotaLimit ?? 10_000
327
+ self.memberQuotaLimit = member.quotaLimit ?? 10000
327
328
  self.showingQuotaSheet = true
328
329
  }
329
330
 
@@ -366,7 +367,7 @@ struct TeamManagementView: View {
366
367
  var updated = self.teams[teamIndex]
367
368
  guard let memberIndex = updated.members.firstIndex(where: { $0.id == memberID }) else { return }
368
369
  var member = updated.members[memberIndex]
369
- member.quotaLimit = hasLimit ? max(1_000, limit) : nil
370
+ member.quotaLimit = hasLimit ? max(1000, limit) : nil
370
371
  updated.members[memberIndex] = member
371
372
  self.teams[teamIndex] = updated
372
373
  self.selectedTeam = updated
@@ -431,14 +432,15 @@ private struct TeamRowView: View {
431
432
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
432
433
  HStack(spacing: RunicSpacing.xs) {
433
434
  Image(systemName: self.team.role.icon)
434
- .font(.body)
435
+ .font(RunicFont.body)
435
436
  .foregroundStyle(self.team.role.color)
436
437
  Text(self.team.name)
437
- .font(.body.weight(.semibold))
438
+ .font(RunicFont.body.weight(.semibold))
438
439
  }
439
440
 
440
- Text("\(self.team.members.count) member\(self.team.members.count == 1 ? "" : "s") · \(self.team.usedQuota, format: .number) / \(self.team.totalQuota, format: .number) credits")
441
- .font(.caption)
441
+ Text(
442
+ "\(self.team.members.count) member\(self.team.members.count == 1 ? "" : "s") · \(self.team.usedQuota, format: .number) / \(self.team.totalQuota, format: .number) credits")
443
+ .font(RunicFont.caption)
442
444
  .foregroundStyle(.tertiary)
443
445
  }
444
446
 
@@ -499,11 +501,11 @@ private struct CreateTeamSheet: View {
499
501
  var body: some View {
500
502
  VStack(alignment: .leading, spacing: RunicSpacing.lg) {
501
503
  Text("Create Team")
502
- .font(.title2.weight(.semibold))
504
+ .font(RunicFont.title2.weight(.semibold))
503
505
 
504
506
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
505
507
  Text("Team Name")
506
- .font(.subheadline.weight(.medium))
508
+ .font(RunicFont.subheadline.weight(.medium))
507
509
  TextField("Enter team name", text: self.$teamName)
508
510
  .textFieldStyle(.roundedBorder)
509
511
  .focused(self.$isNameFieldFocused)
@@ -543,11 +545,11 @@ private struct EditTeamSheet: View {
543
545
  var body: some View {
544
546
  VStack(alignment: .leading, spacing: RunicSpacing.lg) {
545
547
  Text("Edit Team")
546
- .font(.title2.weight(.semibold))
548
+ .font(RunicFont.title2.weight(.semibold))
547
549
 
548
550
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
549
551
  Text("Team Name")
550
- .font(.subheadline.weight(.medium))
552
+ .font(RunicFont.subheadline.weight(.medium))
551
553
  TextField("Enter team name", text: self.$teamName)
552
554
  .textFieldStyle(.roundedBorder)
553
555
  .focused(self.$isNameFieldFocused)
@@ -588,34 +590,34 @@ private struct MemberQuotaSheet: View {
588
590
  VStack(alignment: .leading, spacing: RunicSpacing.lg) {
589
591
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
590
592
  Text("Edit Quota")
591
- .font(.title2.weight(.semibold))
593
+ .font(RunicFont.title2.weight(.semibold))
592
594
  Text("Set a monthly quota for \(self.memberName)")
593
- .font(.footnote)
595
+ .font(RunicFont.footnote)
594
596
  .foregroundStyle(.tertiary)
595
597
  }
596
598
 
597
599
  PreferencesDivider()
598
600
 
599
601
  Toggle("Enable quota limit", isOn: self.$hasLimit)
600
- .font(.subheadline.weight(.medium))
602
+ .font(RunicFont.subheadline.weight(.medium))
601
603
 
602
604
  if self.hasLimit {
603
605
  HStack(spacing: RunicSpacing.sm) {
604
606
  Text("Credits")
605
- .font(.footnote)
607
+ .font(RunicFont.footnote)
606
608
  .foregroundStyle(.secondary)
607
- Stepper(value: self.$quotaLimit, in: 1_000...1_000_000, step: 1_000) {
609
+ Stepper(value: self.$quotaLimit, in: 1000...1_000_000, step: 1000) {
608
610
  TextField("", value: self.$quotaLimit, format: .number)
609
611
  .textFieldStyle(.roundedBorder)
610
612
  .frame(width: 140)
611
613
  }
612
614
  }
613
615
  Text("Monthly credit allocation for this member")
614
- .font(.caption)
616
+ .font(RunicFont.caption)
615
617
  .foregroundStyle(.tertiary)
616
618
  } else {
617
619
  Text("No limit — uses shared team quota")
618
- .font(.caption)
620
+ .font(RunicFont.caption)
619
621
  .foregroundStyle(.tertiary)
620
622
  }
621
623
 
@@ -646,9 +648,9 @@ private struct MemberRoleSheet: View {
646
648
  VStack(alignment: .leading, spacing: RunicSpacing.lg) {
647
649
  VStack(alignment: .leading, spacing: RunicSpacing.xs) {
648
650
  Text("Change Role")
649
- .font(.title2.weight(.semibold))
651
+ .font(RunicFont.title2.weight(.semibold))
650
652
  Text("Update permissions for \(self.memberName)")
651
- .font(.footnote)
653
+ .font(RunicFont.footnote)
652
654
  .foregroundStyle(.tertiary)
653
655
  }
654
656
 
@@ -702,7 +704,6 @@ struct Team: Identifiable, Hashable, Codable {
702
704
  if percent >= 75 { return Color(nsColor: .systemOrange) }
703
705
  return Color(nsColor: .systemBlue)
704
706
  }
705
-
706
707
  }
707
708
 
708
709
  enum TeamRole: String, CaseIterable, Codable {
@@ -713,49 +714,49 @@ enum TeamRole: String, CaseIterable, Codable {
713
714
 
714
715
  var displayName: String {
715
716
  switch self {
716
- case .owner: return "Owner"
717
- case .admin: return "Admin"
718
- case .member: return "Member"
719
- case .viewer: return "Viewer"
717
+ case .owner: "Owner"
718
+ case .admin: "Admin"
719
+ case .member: "Member"
720
+ case .viewer: "Viewer"
720
721
  }
721
722
  }
722
723
 
723
724
  var icon: String {
724
725
  switch self {
725
- case .owner: return "crown.fill"
726
- case .admin: return "star.fill"
727
- case .member: return "person.fill"
728
- case .viewer: return "eye.fill"
726
+ case .owner: "crown.fill"
727
+ case .admin: "star.fill"
728
+ case .member: "person.fill"
729
+ case .viewer: "eye.fill"
729
730
  }
730
731
  }
731
732
 
732
733
  var color: Color {
733
734
  switch self {
734
- case .owner: return Color(nsColor: .systemYellow)
735
- case .admin: return Color(nsColor: .systemBlue)
736
- case .member: return Color(nsColor: .systemGreen)
737
- case .viewer: return Color(nsColor: .systemGray)
735
+ case .owner: Color(nsColor: .systemYellow)
736
+ case .admin: Color(nsColor: .systemBlue)
737
+ case .member: Color(nsColor: .systemGreen)
738
+ case .viewer: Color(nsColor: .systemGray)
738
739
  }
739
740
  }
740
741
 
741
742
  var canInvite: Bool {
742
743
  switch self {
743
- case .owner, .admin: return true
744
- case .member, .viewer: return false
744
+ case .owner, .admin: true
745
+ case .member, .viewer: false
745
746
  }
746
747
  }
747
748
 
748
749
  var canEditQuota: Bool {
749
750
  switch self {
750
- case .owner, .admin: return true
751
- case .member, .viewer: return false
751
+ case .owner, .admin: true
752
+ case .member, .viewer: false
752
753
  }
753
754
  }
754
755
 
755
756
  var canRemoveMembers: Bool {
756
757
  switch self {
757
- case .owner, .admin: return true
758
- case .member, .viewer: return false
758
+ case .owner, .admin: true
759
+ case .member, .viewer: false
759
760
  }
760
761
  }
761
762
  }
@@ -17,19 +17,19 @@ struct TeamMemberRow: View {
17
17
  VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
18
18
  HStack(spacing: RunicSpacing.xs) {
19
19
  Text(self.member.name)
20
- .font(.body.weight(.medium))
20
+ .font(RunicFont.body.weight(.medium))
21
21
 
22
22
  self.roleBadge
23
23
  }
24
24
 
25
25
  Text(self.member.email)
26
- .font(.caption)
26
+ .font(RunicFont.caption)
27
27
  .foregroundStyle(.tertiary)
28
28
 
29
29
  if let quota = self.member.quotaLimit {
30
30
  HStack(spacing: RunicSpacing.xxs) {
31
31
  Text("\(self.member.usedQuota, format: .number) / \(quota, format: .number)")
32
- .font(.caption2)
32
+ .font(RunicFont.caption2)
33
33
  .foregroundStyle(.secondary)
34
34
 
35
35
  UsageProgressBar(
@@ -95,7 +95,7 @@ struct TeamMemberRow: View {
95
95
  .frame(width: 36, height: 36)
96
96
 
97
97
  Text(self.member.initials)
98
- .font(.footnote.weight(.semibold))
98
+ .font(RunicFont.footnote.weight(.semibold))
99
99
  .foregroundStyle(.white)
100
100
  }
101
101
  }
@@ -103,9 +103,9 @@ struct TeamMemberRow: View {
103
103
  private var roleBadge: some View {
104
104
  HStack(spacing: RunicSpacing.xxxs) {
105
105
  Image(systemName: self.member.role.icon)
106
- .font(.caption2)
106
+ .font(RunicFont.caption2)
107
107
  Text(self.member.role.displayName)
108
- .font(.caption2.weight(.medium))
108
+ .font(RunicFont.caption2.weight(.medium))
109
109
  }
110
110
  .padding(.horizontal, RunicSpacing.xxs + RunicSpacing.xxxs)
111
111
  .padding(.vertical, RunicSpacing.xxxs)
@@ -174,8 +174,8 @@ struct TeamMember: Identifiable, Hashable, Codable {
174
174
  name: "You",
175
175
  email: resolvedEmail,
176
176
  role: .owner,
177
- quotaLimit: 50_000,
178
- usedQuota: 12_500,
177
+ quotaLimit: 50000,
178
+ usedQuota: 12500,
179
179
  isCurrentUser: true)
180
180
  }
181
181
  }
@@ -1,6 +1,6 @@
1
- import RunicCore
2
- import Helix
3
1
  import Foundation
2
+ import Helix
3
+ import RunicCore
4
4
 
5
5
  @main
6
6
  enum RunicCLI {
@@ -24,7 +24,10 @@ enum RunicCLI {
24
24
  OptionDefinition(label: "format", names: [.long("format")], help: "Output format: text | json"),
25
25
  ],
26
26
  flags: [
27
- FlagDefinition(label: "verbose", names: [.short("v"), .long("verbose")], help: "Enable verbose logging"),
27
+ FlagDefinition(
28
+ label: "verbose",
29
+ names: [.short("v"), .long("verbose")],
30
+ help: "Enable verbose logging"),
28
31
  FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
29
32
  FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
30
33
  FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
@@ -36,7 +39,10 @@ enum RunicCLI {
36
39
  OptionDefinition(label: "format", names: [.long("format")], help: "Output format: text | json"),
37
40
  ],
38
41
  flags: [
39
- FlagDefinition(label: "verbose", names: [.short("v"), .long("verbose")], help: "Enable verbose logging"),
42
+ FlagDefinition(
43
+ label: "verbose",
44
+ names: [.short("v"), .long("verbose")],
45
+ help: "Enable verbose logging"),
40
46
  FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
41
47
  FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
42
48
  FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
@@ -45,19 +51,37 @@ enum RunicCLI {
45
51
 
46
52
  let insightsSignature = CommandSignature(
47
53
  options: [
48
- OptionDefinition(label: "provider", names: [.long("provider")], help: "Provider to analyze (claude, codex, or all)"),
49
- OptionDefinition(label: "view", names: [.long("view")], help: "View: daily | session | blocks | models | projects | comparative | efficiency"),
54
+ OptionDefinition(
55
+ label: "provider",
56
+ names: [.long("provider")],
57
+ help: "Provider to analyze (claude, codex, or all)"),
58
+ OptionDefinition(
59
+ label: "view",
60
+ names: [.long("view")],
61
+ help: "View: daily | session | blocks | models | projects | comparative | efficiency"),
50
62
  OptionDefinition(label: "project", names: [.long("project")], help: "Filter to a specific project"),
51
- OptionDefinition(label: "timezone", names: [.long("timezone")], help: "Timezone identifier (defaults to local)"),
52
- OptionDefinition(label: "granularity", names: [.long("granularity")], help: "Time granularity: hourly (for daily view)"),
53
- OptionDefinition(label: "gitDirectory", names: [.long("git-directory")], help: "Path to .git directory (defaults to current directory)"),
63
+ OptionDefinition(
64
+ label: "timezone",
65
+ names: [.long("timezone")],
66
+ help: "Timezone identifier (defaults to local)"),
67
+ OptionDefinition(
68
+ label: "granularity",
69
+ names: [.long("granularity")],
70
+ help: "Time granularity: hourly (for daily view)"),
71
+ OptionDefinition(
72
+ label: "gitDirectory",
73
+ names: [.long("git-directory")],
74
+ help: "Path to .git directory (defaults to current directory)"),
54
75
  ],
55
76
  flags: [
56
77
  FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
57
78
  FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
58
79
  FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
59
80
  FlagDefinition(label: "budget", names: [.long("budget")], help: "Include budget tracking information"),
60
- FlagDefinition(label: "withCommits", names: [.long("with-commits")], help: "Link usage entries to git commits"),
81
+ FlagDefinition(
82
+ label: "withCommits",
83
+ names: [.long("with-commits")],
84
+ help: "Link usage entries to git commits"),
61
85
  ])
62
86
 
63
87
  let usageDescriptor = CommandDescriptor(
@@ -118,7 +142,7 @@ enum RunicCLI {
118
142
  }
119
143
 
120
144
  let fetcher = UsageFetcher()
121
- var output: String = ""
145
+ var output = ""
122
146
 
123
147
  for provider in providers {
124
148
  do {
@@ -155,11 +179,14 @@ enum RunicCLI {
155
179
  }
156
180
  }
157
181
 
158
- if isPretty && isJson {
182
+ if isPretty, isJson {
159
183
  if let data = output.data(using: .utf8),
160
184
  let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
161
- let prettyData = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys]),
162
- let pretty = String(data: prettyData, encoding: .utf8) {
185
+ let prettyData = try? JSONSerialization.data(
186
+ withJSONObject: json,
187
+ options: [.prettyPrinted, .sortedKeys]),
188
+ let pretty = String(data: prettyData, encoding: .utf8)
189
+ {
163
190
  print(pretty)
164
191
  } else {
165
192
  print(output)
@@ -183,7 +210,7 @@ enum RunicCLI {
183
210
  guard let p = UsageProvider(rawValue: providerName) else {
184
211
  Self.exit(code: 1, message: "Unknown provider: \(providerName)")
185
212
  }
186
- if p != .claude && p != .codex {
213
+ if p != .claude, p != .codex {
187
214
  Self.exit(code: 1, message: "Cost is only supported for claude and codex")
188
215
  }
189
216
  providers = [p]
@@ -192,7 +219,7 @@ enum RunicCLI {
192
219
  }
193
220
 
194
221
  let fetcher = CostUsageFetcher()
195
- var output: String = ""
222
+ var output = ""
196
223
 
197
224
  for provider in providers {
198
225
  do {
@@ -207,7 +234,7 @@ enum RunicCLI {
207
234
  }
208
235
  }
209
236
 
210
- if isPretty && isJson {
237
+ if isPretty, isJson {
211
238
  print("{\"cost\": \"output available in text format\"}")
212
239
  } else {
213
240
  print(output)
@@ -365,7 +392,8 @@ enum RunicCLI {
365
392
  return summaries.map { summary in
366
393
  guard let projectID = summary.projectID,
367
394
  let budget = budgetsData.budgets[projectID],
368
- budget.enabled else {
395
+ budget.enabled
396
+ else {
369
397
  return ProjectBudgetSummary(
370
398
  provider: summary.provider,
371
399
  projectID: summary.projectID,
@@ -468,7 +496,11 @@ enum RunicCLI {
468
496
  .sorted { $0.requestCount > $1.requestCount }
469
497
  }
470
498
 
471
- private static func renderInsightsWithCommits(_ entriesWithCommits: [UsageWithCommit], isJson: Bool, isPretty: Bool) {
499
+ private static func renderInsightsWithCommits(
500
+ _ entriesWithCommits: [UsageWithCommit],
501
+ isJson: Bool,
502
+ isPretty: Bool)
503
+ {
472
504
  if isJson {
473
505
  let encoder = JSONEncoder()
474
506
  encoder.dateEncodingStrategy = .iso8601
@@ -504,7 +536,7 @@ enum RunicCLI {
504
536
  }
505
537
  }
506
538
 
507
- private static func renderInsightsOutput<T: Encodable>(_ payload: T, isJson: Bool, isPretty: Bool) {
539
+ private static func renderInsightsOutput(_ payload: some Encodable, isJson: Bool, isPretty: Bool) {
508
540
  if isJson {
509
541
  let encoder = JSONEncoder()
510
542
  encoder.dateEncodingStrategy = .iso8601
@@ -525,7 +557,8 @@ enum RunicCLI {
525
557
  for summary in summaries {
526
558
  let project = summary.projectID ?? "all"
527
559
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
528
- print("\(summary.dayKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
560
+ print(
561
+ "\(summary.dayKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
529
562
  }
530
563
  return
531
564
  }
@@ -534,7 +567,8 @@ enum RunicCLI {
534
567
  for summary in summaries {
535
568
  let project = summary.projectID ?? "all"
536
569
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
537
- print("\(summary.sessionID) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
570
+ print(
571
+ "\(summary.sessionID) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
538
572
  }
539
573
  return
540
574
  }
@@ -543,7 +577,8 @@ enum RunicCLI {
543
577
  for summary in summaries {
544
578
  let project = summary.projectID ?? "all"
545
579
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
546
- print("\(summary.start) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
580
+ print(
581
+ "\(summary.start) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
547
582
  }
548
583
  return
549
584
  }
@@ -552,7 +587,8 @@ enum RunicCLI {
552
587
  for summary in summaries {
553
588
  let project = summary.projectID ?? "all"
554
589
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
555
- print("\(summary.model) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
590
+ print(
591
+ "\(summary.model) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
556
592
  }
557
593
  return
558
594
  }
@@ -562,7 +598,8 @@ enum RunicCLI {
562
598
  let project = summary.projectID ?? "unknown"
563
599
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
564
600
  let models = summary.modelsUsed.isEmpty ? "no models" : summary.modelsUsed.joined(separator: ", ")
565
- print("\(project) - \(summary.provider.rawValue) - \(summary.totals.totalTokens) tokens - \(costText) - \(models)")
601
+ print(
602
+ "\(project) - \(summary.provider.rawValue) - \(summary.totals.totalTokens) tokens - \(costText) - \(models)")
566
603
  }
567
604
  return
568
605
  }
@@ -571,7 +608,8 @@ enum RunicCLI {
571
608
  for summary in summaries {
572
609
  let project = summary.projectID ?? "all"
573
610
  let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
574
- print("\(summary.hourKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText) - \(summary.requestCount) requests")
611
+ print(
612
+ "\(summary.hourKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText) - \(summary.requestCount) requests")
575
613
  }
576
614
  return
577
615
  }
@@ -594,7 +632,8 @@ enum RunicCLI {
594
632
  for comparison in comparisons {
595
633
  let costPerToken = String(format: "$%.6f", comparison.costPerToken)
596
634
  let totalCost = String(format: "$%.2f", comparison.totalCost)
597
- print("#\(comparison.rank ?? 0) \(comparison.model) - \(costPerToken)/token - Total: \(totalCost) - Tokens: \(comparison.totalTokens) - Requests: \(comparison.requestCount)")
635
+ print(
636
+ "#\(comparison.rank ?? 0) \(comparison.model) - \(costPerToken)/token - Total: \(totalCost) - Tokens: \(comparison.totalTokens) - Requests: \(comparison.requestCount)")
598
637
  }
599
638
  return
600
639
  }
@@ -605,7 +644,8 @@ enum RunicCLI {
605
644
  let costPerReq = metric.costPerRequest.map { String(format: "$%.4f", $0) } ?? "n/a"
606
645
  let cacheHit = String(format: "%.1f%%", metric.cacheHitRate)
607
646
  let totalCost = metric.totalCost.map { String(format: "$%.2f", $0) } ?? "n/a"
608
- print("\(metric.model) - \(metric.requestCount) reqs - \(tokensPerReq) tok/req - \(costPerReq)/req - Cache: \(cacheHit) - Total: \(totalCost)")
647
+ print(
648
+ "\(metric.model) - \(metric.requestCount) reqs - \(tokensPerReq) tok/req - \(costPerReq)/req - Cache: \(cacheHit) - Total: \(totalCost)")
609
649
  }
610
650
  return
611
651
  }
@@ -727,8 +767,8 @@ enum RunicCLI {
727
767
  private static func creditsString(from remaining: Double) -> String {
728
768
  if remaining >= 1_000_000 {
729
769
  return String(format: "%.1fM", remaining / 1_000_000)
730
- } else if remaining >= 1_000 {
731
- return String(format: "%.1fK", remaining / 1_000)
770
+ } else if remaining >= 1000 {
771
+ return String(format: "%.1fK", remaining / 1000)
732
772
  }
733
773
  return String(format: "%.0f", remaining)
734
774
  }
@@ -736,8 +776,8 @@ enum RunicCLI {
736
776
  private static func formatNumber(_ n: Int) -> String {
737
777
  if n >= 1_000_000 {
738
778
  return String(format: "%.1fM", Double(n) / 1_000_000)
739
- } else if n >= 1_000 {
740
- return String(format: "%.1fK", Double(n) / 1_000)
779
+ } else if n >= 1000 {
780
+ return String(format: "%.1fK", Double(n) / 1000)
741
781
  }
742
782
  return "\(n)"
743
783
  }
@@ -788,7 +828,8 @@ enum RunicCLI {
788
828
  print("insights - Analyze local usage logs")
789
829
  print(" Options:")
790
830
  print(" --provider PROVIDER Provider to analyze (claude, codex, all)")
791
- print(" --view VIEW daily | session | blocks | models | projects | comparative | efficiency")
831
+ print(
832
+ " --view VIEW daily | session | blocks | models | projects | comparative | efficiency")
792
833
  print(" --project PROJECT Filter to a specific project")
793
834
  print(" --timezone TZ Timezone identifier (defaults to local)")
794
835
  print(" --granularity GRAN Time granularity: hourly (for daily view)")
@@ -808,7 +849,7 @@ enum RunicCLI {
808
849
  print(" comparative Model cost-per-token comparison with rankings")
809
850
  print(" efficiency Efficiency metrics (tokens/request, cost/request, cache hit rate)")
810
851
  }
811
- if command != nil && command != "usage" && command != "cost" && command != "insights" {
852
+ if command != nil, command != "usage", command != "cost", command != "insights" {
812
853
  print("Unknown command: \(command ?? "")")
813
854
  }
814
855
  Foundation.exit(0)
@@ -820,7 +861,7 @@ enum RunicCLI {
820
861
  }
821
862
 
822
863
  private static func exit(code: Int32, message: String? = nil) -> Never {
823
- if let message = message {
864
+ if let message {
824
865
  FileHandle.standardError.write((message + "\n").data(using: .utf8)!)
825
866
  }
826
867
  Foundation.exit(code)
@@ -834,7 +875,7 @@ struct UsageCommand {
834
875
  var json: Bool = false
835
876
  var pretty: Bool = false
836
877
  var noColor: Bool = false
837
- var provider: String? = nil
878
+ var provider: String?
838
879
  var format: String = "text"
839
880
  }
840
881
 
@@ -843,7 +884,7 @@ struct CostCommand {
843
884
  var json: Bool = false
844
885
  var pretty: Bool = false
845
886
  var noColor: Bool = false
846
- var provider: String? = nil
887
+ var provider: String?
847
888
  var format: String = "text"
848
889
  var refresh: Bool = false
849
890
  }
@@ -852,12 +893,12 @@ struct InsightsCommand {
852
893
  var json: Bool = false
853
894
  var pretty: Bool = false
854
895
  var noColor: Bool = false
855
- var provider: String? = nil
896
+ var provider: String?
856
897
  var view: String = "daily"
857
- var project: String? = nil
858
- var timezone: String? = nil
859
- var granularity: String? = nil
860
- var gitDirectory: String? = nil
898
+ var project: String?
899
+ var timezone: String?
900
+ var granularity: String?
901
+ var gitDirectory: String?
861
902
  var budget: Bool = false
862
903
  var withCommits: Bool = false
863
904
  }
@@ -1,5 +1,5 @@
1
- import RunicCore
2
1
  import Foundation
2
+ import RunicCore
3
3
 
4
4
  enum CLIRenderer {
5
5
  static func renderText(