@sriinnu/runic 1.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 (584) hide show
  1. package/.swiftformat +50 -0
  2. package/.swiftlint.yml +160 -0
  3. package/CHANGELOG.md +307 -0
  4. package/CONTRIBUTING.md +456 -0
  5. package/Icon.icns +0 -0
  6. package/Icon.icon/Assets/runic.png +0 -0
  7. package/Icon.icon/icon.json +36 -0
  8. package/LICENSE +21 -0
  9. package/Package.resolved +51 -0
  10. package/Package.swift +136 -0
  11. package/Packages/Helix/LICENSE +21 -0
  12. package/Packages/Helix/Package.swift +29 -0
  13. package/Packages/Helix/README.md +976 -0
  14. package/Packages/Helix/Sources/Helix/Binding.swift +111 -0
  15. package/Packages/Helix/Sources/Helix/CommandParser.swift +160 -0
  16. package/Packages/Helix/Sources/Helix/CommandSignature.swift +117 -0
  17. package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +72 -0
  18. package/Packages/Helix/Sources/Helix/HelpGenerator.swift +175 -0
  19. package/Packages/Helix/Sources/Helix/NameSpecification.swift +98 -0
  20. package/Packages/Helix/Sources/Helix/ParsableCommand.swift +204 -0
  21. package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +44 -0
  22. package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +34 -0
  23. package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +91 -0
  24. package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +94 -0
  25. package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +167 -0
  26. package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +99 -0
  27. package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +146 -0
  28. package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +97 -0
  29. package/Packages/Helix/Sources/Helix/Program.swift +204 -0
  30. package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +367 -0
  31. package/Packages/Silo/LICENSE +21 -0
  32. package/Packages/Silo/Package.swift +34 -0
  33. package/Packages/Silo/README.md +466 -0
  34. package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +169 -0
  35. package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +365 -0
  36. package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +594 -0
  37. package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +326 -0
  38. package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +154 -0
  39. package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +119 -0
  40. package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +121 -0
  41. package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +391 -0
  42. package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +240 -0
  43. package/Packages/Silo/Sources/Silo/CryptoSupport.swift +86 -0
  44. package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +172 -0
  45. package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +172 -0
  46. package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +105 -0
  47. package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +191 -0
  48. package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +41 -0
  49. package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +110 -0
  50. package/README.md +109 -0
  51. package/RuniciOS/App/RuniciOSApp.swift +105 -0
  52. package/RuniciOS/Core/iOSUsageStore.swift +192 -0
  53. package/RuniciOS/Views/ProviderDetailView.swift +312 -0
  54. package/RuniciOS/Views/ProviderListView.swift +228 -0
  55. package/RuniciOS/Widgets/LargeWidget.swift +367 -0
  56. package/RuniciOS/Widgets/LiveActivity.swift +342 -0
  57. package/RuniciOS/Widgets/LockScreenWidget.swift +273 -0
  58. package/RuniciOS/Widgets/MediumWidget.swift +370 -0
  59. package/RuniciOS/Widgets/RunicWidget.swift +274 -0
  60. package/RuniciOS/Widgets/SmallWidget.swift +281 -0
  61. package/RuniciOS/Widgets/WidgetModels.swift +285 -0
  62. package/RuniciOS/Widgets/WidgetTimeline.swift +270 -0
  63. package/Scripts/CREATION-SUMMARY.md +367 -0
  64. package/Scripts/DIRECTORY-STRUCTURE.md +358 -0
  65. package/Scripts/GETTING-STARTED-CHECKLIST.md +364 -0
  66. package/Scripts/INDEX.md +365 -0
  67. package/Scripts/QUICK-REFERENCE.md +164 -0
  68. package/Scripts/README-SCRIPTS.md +926 -0
  69. package/Scripts/build-android.sh +526 -0
  70. package/Scripts/build-ios.sh +529 -0
  71. package/Scripts/build-macos.sh +603 -0
  72. package/Scripts/build-windows.sh +504 -0
  73. package/Scripts/build_icon.sh +49 -0
  74. package/Scripts/changelog-to-html.sh +89 -0
  75. package/Scripts/check-release-assets.sh +10 -0
  76. package/Scripts/ci-config.yml +474 -0
  77. package/Scripts/compile_and_run.sh +219 -0
  78. package/Scripts/compile_and_run_adhoc.sh +4 -0
  79. package/Scripts/docs-list.mjs +119 -0
  80. package/Scripts/make_appcast.sh +58 -0
  81. package/Scripts/package_app.sh +409 -0
  82. package/Scripts/release-lib.sh +214 -0
  83. package/Scripts/release.sh +76 -0
  84. package/Scripts/setup-all.sh +387 -0
  85. package/Scripts/setup-ios.sh +404 -0
  86. package/Scripts/setup-react-native.sh +523 -0
  87. package/Scripts/sign-and-notarize.sh +122 -0
  88. package/Scripts/test_live_update.sh +38 -0
  89. package/Scripts/validate_changelog.sh +23 -0
  90. package/Scripts/verify_appcast.sh +92 -0
  91. package/Sources/Runic/About.swift +93 -0
  92. package/Sources/Runic/AppNotifications.swift +115 -0
  93. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +657 -0
  94. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +518 -0
  95. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +810 -0
  96. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +87 -0
  97. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +1379 -0
  98. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +337 -0
  99. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +395 -0
  100. package/Sources/Runic/Controllers/StatusItemController.swift +360 -0
  101. package/Sources/Runic/Core/BudgetNotificationManager.swift +80 -0
  102. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +103 -0
  103. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +154 -0
  104. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +206 -0
  105. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +95 -0
  106. package/Sources/Runic/Core/Rendering/IconRenderer.swift +1216 -0
  107. package/Sources/Runic/Core/Rendering/IconView.swift +120 -0
  108. package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +71 -0
  109. package/Sources/Runic/Core/RunicAnimation.swift +53 -0
  110. package/Sources/Runic/Core/RunicColors.swift +73 -0
  111. package/Sources/Runic/Core/RunicCornerRadius.swift +15 -0
  112. package/Sources/Runic/Core/RunicSpacing.swift +68 -0
  113. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +143 -0
  114. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +143 -0
  115. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +143 -0
  116. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +143 -0
  117. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +143 -0
  118. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +158 -0
  119. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +143 -0
  120. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +143 -0
  121. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +143 -0
  122. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +143 -0
  123. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +143 -0
  124. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +143 -0
  125. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +143 -0
  126. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +143 -0
  127. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +143 -0
  128. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +143 -0
  129. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +30 -0
  130. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +143 -0
  131. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +143 -0
  132. package/Sources/Runic/Core/Stores/SettingsStore.swift +1702 -0
  133. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +143 -0
  134. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +20 -0
  135. package/Sources/Runic/Core/Stores/UsageStore+Status.swift +189 -0
  136. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +33 -0
  137. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +67 -0
  138. package/Sources/Runic/Core/Stores/UsageStore.swift +3564 -0
  139. package/Sources/Runic/Core/Stores/XAITokenStore.swift +143 -0
  140. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +143 -0
  141. package/Sources/Runic/Core/UsageExporter.swift +193 -0
  142. package/Sources/Runic/Date+RelativeDescription.swift +21 -0
  143. package/Sources/Runic/HiddenWindowView.swift +32 -0
  144. package/Sources/Runic/InstallOrigin.swift +9 -0
  145. package/Sources/Runic/KeyboardShortcuts+Names.swift +6 -0
  146. package/Sources/Runic/MouseLocationReader.swift +65 -0
  147. package/Sources/Runic/Notifications+Runic.swift +6 -0
  148. package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +503 -0
  149. package/Sources/Runic/ProviderRegistry.swift +100 -0
  150. package/Sources/Runic/ProviderSwitcherButtons.swift +267 -0
  151. package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +11 -0
  152. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +14 -0
  153. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +24 -0
  154. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +52 -0
  155. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +42 -0
  156. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +24 -0
  157. package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +26 -0
  158. package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +14 -0
  159. package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +18 -0
  160. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +15 -0
  161. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +24 -0
  162. package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +193 -0
  163. package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +110 -0
  164. package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +28 -0
  165. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +15 -0
  166. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +24 -0
  167. package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +12 -0
  168. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +15 -0
  169. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +24 -0
  170. package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +20 -0
  171. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +15 -0
  172. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +24 -0
  173. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +24 -0
  174. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +32 -0
  175. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +24 -0
  176. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +24 -0
  177. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +24 -0
  178. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +24 -0
  179. package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +15 -0
  180. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +48 -0
  181. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +66 -0
  182. package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +12 -0
  183. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +99 -0
  184. package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +18 -0
  185. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +24 -0
  186. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +24 -0
  187. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +24 -0
  188. package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +2 -0
  189. package/Sources/Runic/Resources/ProviderIcon-auggie.svg +3 -0
  190. package/Sources/Runic/Resources/ProviderIcon-azure.svg +5 -0
  191. package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +7 -0
  192. package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +3 -0
  193. package/Sources/Runic/Resources/ProviderIcon-claude.svg +1 -0
  194. package/Sources/Runic/Resources/ProviderIcon-codex.svg +1 -0
  195. package/Sources/Runic/Resources/ProviderIcon-cohere.svg +3 -0
  196. package/Sources/Runic/Resources/ProviderIcon-copilot.svg +1 -0
  197. package/Sources/Runic/Resources/ProviderIcon-cursor.svg +3 -0
  198. package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +4 -0
  199. package/Sources/Runic/Resources/ProviderIcon-factory.svg +3 -0
  200. package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +3 -0
  201. package/Sources/Runic/Resources/ProviderIcon-gemini.svg +1 -0
  202. package/Sources/Runic/Resources/ProviderIcon-groq.svg +1 -0
  203. package/Sources/Runic/Resources/ProviderIcon-kimi.svg +4 -0
  204. package/Sources/Runic/Resources/ProviderIcon-minimax.svg +1 -0
  205. package/Sources/Runic/Resources/ProviderIcon-mistral.svg +5 -0
  206. package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +7 -0
  207. package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +4 -0
  208. package/Sources/Runic/Resources/ProviderIcon-qwen.svg +9 -0
  209. package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +4 -0
  210. package/Sources/Runic/Resources/ProviderIcon-together.svg +5 -0
  211. package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +17 -0
  212. package/Sources/Runic/Resources/ProviderIcon-xai.svg +3 -0
  213. package/Sources/Runic/Resources/ProviderIcon-zai.svg +5 -0
  214. package/Sources/Runic/Resources/RunicMenubarIcon.svg +7 -0
  215. package/Sources/Runic/RunicApp.swift +357 -0
  216. package/Sources/Runic/SessionQuotaNotifications.swift +58 -0
  217. package/Sources/Runic/UsagePaceText.swift +52 -0
  218. package/Sources/Runic/UsageProgressBar.swift +224 -0
  219. package/Sources/Runic/Utilities/DisplayLink.swift +96 -0
  220. package/Sources/Runic/Utilities/LoadingPattern.swift +60 -0
  221. package/Sources/Runic/Utilities/PerformanceConstants.swift +85 -0
  222. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +256 -0
  223. package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +22 -0
  224. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +275 -0
  225. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +377 -0
  226. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +312 -0
  227. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +312 -0
  228. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +228 -0
  229. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +228 -0
  230. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +162 -0
  231. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +64 -0
  232. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +268 -0
  233. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +104 -0
  234. package/Sources/Runic/Views/Menu/MenuCardView.swift +1900 -0
  235. package/Sources/Runic/Views/Menu/MenuContent.swift +150 -0
  236. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +369 -0
  237. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +42 -0
  238. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +235 -0
  239. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +141 -0
  240. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +209 -0
  241. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +220 -0
  242. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +88 -0
  243. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +201 -0
  244. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +398 -0
  245. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +232 -0
  246. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +417 -0
  247. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +190 -0
  248. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +132 -0
  249. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +618 -0
  250. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +305 -0
  251. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +271 -0
  252. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +268 -0
  253. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +422 -0
  254. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +641 -0
  255. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +344 -0
  256. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +230 -0
  257. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +322 -0
  258. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +469 -0
  259. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +231 -0
  260. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +50 -0
  261. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +497 -0
  262. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +288 -0
  263. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +3435 -0
  264. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +25 -0
  265. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +59 -0
  266. package/Sources/Runic/Views/Preferences/PreferencesView.swift +206 -0
  267. package/Sources/Runic/Views/Rating/QualityRatingView.swift +238 -0
  268. package/Sources/Runic/Views/Team/TeamDashboardView.swift +371 -0
  269. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +175 -0
  270. package/Sources/Runic/Views/Team/TeamManagementView.swift +761 -0
  271. package/Sources/Runic/Views/Team/TeamMemberRow.swift +181 -0
  272. package/Sources/RunicCLI/CLIEntry.swift +942 -0
  273. package/Sources/RunicCLI/CLIRenderer.swift +87 -0
  274. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +588 -0
  275. package/Sources/RunicCLI/Commands/ModelsCommand.swift +362 -0
  276. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +439 -0
  277. package/Sources/RunicCLI/Commands/ResetCommand.swift +429 -0
  278. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +168 -0
  279. package/Sources/RunicClaudeWatchdog/main.swift +122 -0
  280. package/Sources/RunicClaudeWebProbe/main.swift +62 -0
  281. package/Sources/RunicCore/AlertRuleStore.swift +187 -0
  282. package/Sources/RunicCore/BrowserCookieImportOrder.swift +25 -0
  283. package/Sources/RunicCore/CopilotUsageModels.swift +84 -0
  284. package/Sources/RunicCore/CostUsageFetcher.swift +119 -0
  285. package/Sources/RunicCore/CostUsageModels.swift +479 -0
  286. package/Sources/RunicCore/CreditsModels.swift +50 -0
  287. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +269 -0
  288. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +622 -0
  289. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +500 -0
  290. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +679 -0
  291. package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +135 -0
  292. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +173 -0
  293. package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +79 -0
  294. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +86 -0
  295. package/Sources/RunicCore/Logging/RunicLog.swift +123 -0
  296. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +371 -0
  297. package/Sources/RunicCore/OpenAIDashboardModels.swift +210 -0
  298. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +676 -0
  299. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +492 -0
  300. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +51 -0
  301. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +483 -0
  302. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +558 -0
  303. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +204 -0
  304. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +78 -0
  305. package/Sources/RunicCore/PathEnvironment.swift +482 -0
  306. package/Sources/RunicCore/Performance/PerformanceModels.swift +371 -0
  307. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +409 -0
  308. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +178 -0
  309. package/Sources/RunicCore/ProjectBudgetStore.swift +99 -0
  310. package/Sources/RunicCore/ProviderCostSnapshot.swift +29 -0
  311. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +66 -0
  312. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +685 -0
  313. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +83 -0
  314. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +142 -0
  315. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +141 -0
  316. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +186 -0
  317. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +126 -0
  318. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +168 -0
  319. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +84 -0
  320. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +75 -0
  321. package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +332 -0
  322. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +180 -0
  323. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +137 -0
  324. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +207 -0
  325. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +654 -0
  326. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +28 -0
  327. package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +480 -0
  328. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +780 -0
  329. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +146 -0
  330. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +269 -0
  331. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +100 -0
  332. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +243 -0
  333. package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +125 -0
  334. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +24 -0
  335. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +181 -0
  336. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +83 -0
  337. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +85 -0
  338. package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +123 -0
  339. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +114 -0
  340. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +100 -0
  341. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +291 -0
  342. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +386 -0
  343. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +65 -0
  344. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +594 -0
  345. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +85 -0
  346. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +108 -0
  347. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +398 -0
  348. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +65 -0
  349. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +1275 -0
  350. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +84 -0
  351. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +75 -0
  352. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +65 -0
  353. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +835 -0
  354. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +83 -0
  355. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +71 -0
  356. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +83 -0
  357. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +77 -0
  358. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +92 -0
  359. package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +54 -0
  360. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +171 -0
  361. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +911 -0
  362. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +84 -0
  363. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +75 -0
  364. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +87 -0
  365. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +207 -0
  366. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +84 -0
  367. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +75 -0
  368. package/Sources/RunicCore/Providers/ProviderBranding.swift +25 -0
  369. package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +17 -0
  370. package/Sources/RunicCore/Providers/ProviderDescriptor.swift +129 -0
  371. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +263 -0
  372. package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +98 -0
  373. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +501 -0
  374. package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +68 -0
  375. package/Sources/RunicCore/Providers/Providers.swift +184 -0
  376. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +84 -0
  377. package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +30 -0
  378. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +317 -0
  379. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +84 -0
  380. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +75 -0
  381. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +83 -0
  382. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +75 -0
  383. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +123 -0
  384. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +48 -0
  385. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +144 -0
  386. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +83 -0
  387. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +75 -0
  388. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +72 -0
  389. package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +41 -0
  390. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +724 -0
  391. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +385 -0
  392. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +310 -0
  393. package/Sources/RunicCore/Sync/SyncProtocol.swift +288 -0
  394. package/Sources/RunicCore/Sync/SyncRecord.swift +405 -0
  395. package/Sources/RunicCore/Sync/SyncUsageExample.swift +294 -0
  396. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +775 -0
  397. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +333 -0
  398. package/Sources/RunicCore/Team/TeamModels.swift +583 -0
  399. package/Sources/RunicCore/Team/TeamShowbackExporter.swift +495 -0
  400. package/Sources/RunicCore/Team/TeamStore.swift +792 -0
  401. package/Sources/RunicCore/TextParsing.swift +55 -0
  402. package/Sources/RunicCore/UsageFetcher.swift +694 -0
  403. package/Sources/RunicCore/UsageFormatter.swift +410 -0
  404. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +329 -0
  405. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +448 -0
  406. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +550 -0
  407. package/Sources/RunicCore/UsageLedger/UsageLedger.swift +29 -0
  408. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +660 -0
  409. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +209 -0
  410. package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +417 -0
  411. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +245 -0
  412. package/Sources/RunicCore/UsagePace.swift +81 -0
  413. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +73 -0
  414. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +74 -0
  415. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +208 -0
  416. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +455 -0
  417. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +132 -0
  418. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +589 -0
  419. package/Sources/RunicCore/WidgetSnapshot.swift +183 -0
  420. package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +14 -0
  421. package/Sources/RunicMacros/ProviderRegistrationMacros.swift +216 -0
  422. package/Sources/RunicWidget/RunicWidgetBundle.swift +79 -0
  423. package/Sources/RunicWidget/RunicWidgetProvider.swift +283 -0
  424. package/Sources/RunicWidget/RunicWidgetViews.swift +680 -0
  425. package/Tests/RunicTests/AntigravityStatusProbeTests.swift +50 -0
  426. package/Tests/RunicTests/AppDelegateTests.swift +43 -0
  427. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +22 -0
  428. package/Tests/RunicTests/CLIArgumentParsingTests.swift +70 -0
  429. package/Tests/RunicTests/CLICostTests.swift +65 -0
  430. package/Tests/RunicTests/CLIProviderSelectionTests.swift +22 -0
  431. package/Tests/RunicTests/CLISnapshotTests.swift +72 -0
  432. package/Tests/RunicTests/CLIWebFallbackTests.swift +58 -0
  433. package/Tests/RunicTests/ClaudeOAuthTests.swift +164 -0
  434. package/Tests/RunicTests/ClaudeResilienceTests.swift +30 -0
  435. package/Tests/RunicTests/ClaudeUsageTests.swift +370 -0
  436. package/Tests/RunicTests/CodexOAuthTests.swift +122 -0
  437. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +82 -0
  438. package/Tests/RunicTests/CopilotTokenResolverTests.swift +75 -0
  439. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +75 -0
  440. package/Tests/RunicTests/CostUsageDecodingTests.swift +394 -0
  441. package/Tests/RunicTests/CostUsagePricingTests.swift +50 -0
  442. package/Tests/RunicTests/CostUsageScannerTests.swift +700 -0
  443. package/Tests/RunicTests/CursorStatusProbeTests.swift +323 -0
  444. package/Tests/RunicTests/FactoryStatusProbeTests.swift +69 -0
  445. package/Tests/RunicTests/GeminiAPITestHelpers.swift +94 -0
  446. package/Tests/RunicTests/GeminiLoginAlertTests.swift +28 -0
  447. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +283 -0
  448. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +279 -0
  449. package/Tests/RunicTests/GeminiStatusProbeTests.swift +233 -0
  450. package/Tests/RunicTests/GeminiTestEnvironment.swift +87 -0
  451. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +73 -0
  452. package/Tests/RunicTests/InstallOriginTests.swift +18 -0
  453. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +9 -0
  454. package/Tests/RunicTests/LiveAccountTests.swift +24 -0
  455. package/Tests/RunicTests/LoadingPatternTests.swift +35 -0
  456. package/Tests/RunicTests/MenuCardModelTests.swift +545 -0
  457. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +8 -0
  458. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +8 -0
  459. package/Tests/RunicTests/MiniMaxParsingTests.swift +68 -0
  460. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +8 -0
  461. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +112 -0
  462. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +17 -0
  463. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +75 -0
  464. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +27 -0
  465. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +134 -0
  466. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +53 -0
  467. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +92 -0
  468. package/Tests/RunicTests/PathBuilderTests.swift +237 -0
  469. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +160 -0
  470. package/Tests/RunicTests/ProviderIconResourcesTests.swift +38 -0
  471. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +16 -0
  472. package/Tests/RunicTests/ProviderRegistryTests.swift +29 -0
  473. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +202 -0
  474. package/Tests/RunicTests/ProviderToggleStoreTests.swift +51 -0
  475. package/Tests/RunicTests/RunicTests.swift +120 -0
  476. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +36 -0
  477. package/Tests/RunicTests/SettingsStoreTests.swift +161 -0
  478. package/Tests/RunicTests/StatusItemAnimationTests.swift +49 -0
  479. package/Tests/RunicTests/StatusMenuTests.swift +434 -0
  480. package/Tests/RunicTests/StatusProbeTests.swift +377 -0
  481. package/Tests/RunicTests/SubprocessRunnerTests.swift +20 -0
  482. package/Tests/RunicTests/SubscriptionDetectionTests.swift +65 -0
  483. package/Tests/RunicTests/TTYCommandRunnerTests.swift +148 -0
  484. package/Tests/RunicTests/TTYIntegrationTests.swift +41 -0
  485. package/Tests/RunicTests/TeamShowbackExporterTests.swift +159 -0
  486. package/Tests/RunicTests/TextParsingTests.swift +12 -0
  487. package/Tests/RunicTests/UsageFormatterTests.swift +112 -0
  488. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +172 -0
  489. package/Tests/RunicTests/UsageLedgerForecastTests.swift +213 -0
  490. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +83 -0
  491. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +170 -0
  492. package/Tests/RunicTests/UsagePaceTests.swift +77 -0
  493. package/Tests/RunicTests/UsagePaceTextTests.swift +109 -0
  494. package/Tests/RunicTests/UsagePolicyEngineTests.swift +91 -0
  495. package/Tests/RunicTests/WidgetSnapshotTests.swift +45 -0
  496. package/Tests/RunicTests/ZaiProviderTests.swift +61 -0
  497. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +6 -0
  498. package/TestsLinux/PlatformGatingTests.swift +46 -0
  499. package/appcast.xml +105 -0
  500. package/assets/logo-concept-stack.svg +24 -0
  501. package/assets/logo-minimal-bars.svg +15 -0
  502. package/assets/logo.svg +20 -0
  503. package/assets/logo2.svg +43 -0
  504. package/bin/docs-list +5 -0
  505. package/bin/install-runic-cli.sh +32 -0
  506. package/build-and-run.sh +369 -0
  507. package/docs/.nojekyll +1 -0
  508. package/docs/CNAME +1 -0
  509. package/docs/antigravity.md +75 -0
  510. package/docs/architecture.md +32 -0
  511. package/docs/claude.md +93 -0
  512. package/docs/cli-commands/integration_guide.md +371 -0
  513. package/docs/cli-commands/quick_reference.md +362 -0
  514. package/docs/cli-commands/readme.md +361 -0
  515. package/docs/cli.md +156 -0
  516. package/docs/cloudkit_sync_guide.md +495 -0
  517. package/docs/codebase_review.md +288 -0
  518. package/docs/codex-oauth.md +635 -0
  519. package/docs/codex.md +104 -0
  520. package/docs/codexbar.png +0 -0
  521. package/docs/copilot.md +44 -0
  522. package/docs/cursor.md +46 -0
  523. package/docs/design/designcomparisonchart.md +510 -0
  524. package/docs/design/implementationguide.md +792 -0
  525. package/docs/design/menubaricondesignconcepts.md +796 -0
  526. package/docs/design/mockups/hybridiconconcept.svg +62 -0
  527. package/docs/design/mockups/infinityiconconcept.svg +41 -0
  528. package/docs/design/mockups/infinityiconstates.svg +129 -0
  529. package/docs/design/mockups/wavelogorefined.svg +48 -0
  530. package/docs/design/quick_summary.md +426 -0
  531. package/docs/design/readme.md +478 -0
  532. package/docs/design/sizespecifications.md +575 -0
  533. package/docs/design-system.md +836 -0
  534. package/docs/factory.md +103 -0
  535. package/docs/gemini.md +73 -0
  536. package/docs/icon.md +37 -0
  537. package/docs/icon.png +0 -0
  538. package/docs/index.html +175 -0
  539. package/docs/keychain_fix.md +274 -0
  540. package/docs/minimax.md +71 -0
  541. package/docs/packaging.md +25 -0
  542. package/docs/progress-bar-improvements.md +240 -0
  543. package/docs/progress-bar-quick-reference.md +283 -0
  544. package/docs/progress-bar-technical-details.md +432 -0
  545. package/docs/progress-bar-visual-test.swift +375 -0
  546. package/docs/provider.md +202 -0
  547. package/docs/providers.md +86 -0
  548. package/docs/refactor/macros.md +35 -0
  549. package/docs/refresh-loop.md +22 -0
  550. package/docs/releasing-homebrew.md +35 -0
  551. package/docs/releasing.md +121 -0
  552. package/docs/research/concrete_todo_2026-02-22.md +47 -0
  553. package/docs/research/core-feature-research.md +248 -0
  554. package/docs/research/feature_uiux_roadmap_2026-02-22.md +202 -0
  555. package/docs/research/parity_matrix.md +49 -0
  556. package/docs/research/provider_expansion_candidates_2026-02-22.md +80 -0
  557. package/docs/research/provider_model_token_project_matrix_2026-02-23.md +46 -0
  558. package/docs/research/release_gate_checklist.md +57 -0
  559. package/docs/site.css +505 -0
  560. package/docs/sparkle.md +28 -0
  561. package/docs/status.md +28 -0
  562. package/docs/sync/implementation.md +393 -0
  563. package/docs/sync/readme.md +317 -0
  564. package/docs/sync_files_summary.md +391 -0
  565. package/docs/sync_quick_reference.md +225 -0
  566. package/docs/todo.md +11 -0
  567. package/docs/ui.md +29 -0
  568. package/docs/web-integration.md +9 -0
  569. package/docs/widgets.md +85 -0
  570. package/docs/workflows/error-handling-improvements.md +434 -0
  571. package/docs/workflows/folder_structure.md +299 -0
  572. package/docs/workflows/loading_states.md +446 -0
  573. package/docs/workflows/loading_states_examples.md +565 -0
  574. package/docs/workflows/loading_states_flow.md +430 -0
  575. package/docs/workflows/performance_optimizations.md +457 -0
  576. package/docs/workflows/phase1-cli-testing-report.md +313 -0
  577. package/docs/workflows/quick_reference.md +228 -0
  578. package/docs/workflows/readme.md +59 -0
  579. package/docs/workflows/recent_improvements.md +275 -0
  580. package/docs/workflows/sync_enablement_summary.md +400 -0
  581. package/docs/zai.md +43 -0
  582. package/package.json +36 -0
  583. package/runic.png +0 -0
  584. package/version.env +2 -0
@@ -0,0 +1,65 @@
1
+ import RunicMacroSupport
2
+ import Foundation
3
+
4
+ @ProviderDescriptorRegistration
5
+ @ProviderDescriptorDefinition
6
+ public enum CursorProviderDescriptor {
7
+ static func makeDescriptor() -> ProviderDescriptor {
8
+ ProviderDescriptor(
9
+ id: .cursor,
10
+ metadata: ProviderMetadata(
11
+ id: .cursor,
12
+ displayName: "Cursor",
13
+ sessionLabel: "Plan",
14
+ weeklyLabel: "On-Demand",
15
+ opusLabel: nil,
16
+ supportsOpus: false,
17
+ supportsCredits: true,
18
+ creditsHint: "On-demand usage beyond included plan limits.",
19
+ toggleTitle: "Show Cursor usage",
20
+ cliName: "cursor",
21
+ defaultEnabled: false,
22
+ isPrimaryProvider: false,
23
+ usesAccountFallback: false,
24
+ browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,
25
+ dashboardURL: "https://cursor.com/dashboard?tab=usage",
26
+ statusPageURL: "https://status.cursor.com",
27
+ statusLinkURL: nil,
28
+ usageCoverage: ProviderUsageCoverage(
29
+ supportsModelBreakdown: false,
30
+ supportsTokenMetrics: false,
31
+ supportsProjectAttribution: false)),
32
+ branding: ProviderBranding(
33
+ iconStyle: .cursor,
34
+ iconResourceName: "ProviderIcon-cursor",
35
+ color: ProviderColor(red: 0 / 255, green: 191 / 255, blue: 165 / 255)),
36
+ tokenCost: ProviderTokenCostConfig(
37
+ supportsTokenCost: false,
38
+ noDataMessage: { "Cursor cost summary is not supported." }),
39
+ fetchPlan: ProviderFetchPlan(
40
+ sourceModes: [.auto, .cli],
41
+ pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CursorStatusFetchStrategy()] })),
42
+ cli: ProviderCLIConfig(
43
+ name: "cursor",
44
+ versionDetector: nil))
45
+ }
46
+ }
47
+
48
+ struct CursorStatusFetchStrategy: ProviderFetchStrategy {
49
+ let id: String = "cursor.web"
50
+ let kind: ProviderFetchKind = .web
51
+
52
+ func isAvailable(_: ProviderFetchContext) async -> Bool { true }
53
+
54
+ func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {
55
+ let probe = CursorStatusProbe()
56
+ let snap = try await probe.fetch()
57
+ return self.makeResult(
58
+ usage: snap.toUsageSnapshot(),
59
+ sourceLabel: "web")
60
+ }
61
+
62
+ func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
63
+ false
64
+ }
65
+ }
@@ -0,0 +1,594 @@
1
+ import Foundation
2
+ import Silo
3
+
4
+ #if os(macOS)
5
+
6
+ private let cursorCookieImportOrder: BrowserCookieImportOrder =
7
+ ProviderDefaults.metadata[.cursor]?.browserCookieOrder ?? Browser.defaultImportOrder
8
+
9
+ // MARK: - Cursor Cookie Importer
10
+
11
+ /// Imports Cursor session cookies from browser cookies.
12
+ public enum CursorCookieImporter {
13
+ private static let cookieClient = BrowserCookieClient()
14
+ private static let sessionCookieNames: Set<String> = [
15
+ "WorkosCursorSessionToken",
16
+ "__Secure-next-auth.session-token",
17
+ "next-auth.session-token",
18
+ ]
19
+
20
+ public struct SessionInfo: Sendable {
21
+ public let cookies: [HTTPCookie]
22
+ public let sourceLabel: String
23
+
24
+ public init(cookies: [HTTPCookie], sourceLabel: String) {
25
+ self.cookies = cookies
26
+ self.sourceLabel = sourceLabel
27
+ }
28
+
29
+ public var cookieHeader: String {
30
+ self.cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
31
+ }
32
+ }
33
+
34
+ /// Attempts to import Cursor cookies using the standard browser import order.
35
+ public static func importSession(logger: ((String) -> Void)? = nil) throws -> SessionInfo {
36
+ let log: (String) -> Void = { msg in logger?("[cursor-cookie] \(msg)") }
37
+
38
+ let cookieDomains = ["cursor.com", "cursor.sh"]
39
+ for browserSource in cursorCookieImportOrder {
40
+ do {
41
+ let query = BrowserCookieQuery(domains: cookieDomains)
42
+ let sources = try Self.cookieClient.records(
43
+ matching: query,
44
+ in: browserSource)
45
+ for source in sources where !source.records.isEmpty {
46
+ let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)
47
+ if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {
48
+ log("Found \(httpCookies.count) Cursor cookies in \(source.label)")
49
+ return SessionInfo(cookies: httpCookies, sourceLabel: source.label)
50
+ } else {
51
+ log("\(source.label) cookies found, but no Cursor session cookie present")
52
+ }
53
+ }
54
+ } catch {
55
+ log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
56
+ }
57
+ }
58
+
59
+ throw CursorStatusProbeError.noSessionCookie
60
+ }
61
+
62
+ /// Check if Cursor session cookies are available
63
+ public static func hasSession(logger: ((String) -> Void)? = nil) -> Bool {
64
+ do {
65
+ let session = try self.importSession(logger: logger)
66
+ return !session.cookies.isEmpty
67
+ } catch {
68
+ return false
69
+ }
70
+ }
71
+ }
72
+
73
+ // MARK: - Cursor API Models
74
+
75
+ public struct CursorUsageSummary: Codable, Sendable {
76
+ public let billingCycleStart: String?
77
+ public let billingCycleEnd: String?
78
+ public let membershipType: String?
79
+ public let limitType: String?
80
+ public let isUnlimited: Bool?
81
+ public let autoModelSelectedDisplayMessage: String?
82
+ public let namedModelSelectedDisplayMessage: String?
83
+ public let individualUsage: CursorIndividualUsage?
84
+ public let teamUsage: CursorTeamUsage?
85
+ }
86
+
87
+ public struct CursorIndividualUsage: Codable, Sendable {
88
+ public let plan: CursorPlanUsage?
89
+ public let onDemand: CursorOnDemandUsage?
90
+ }
91
+
92
+ public struct CursorPlanUsage: Codable, Sendable {
93
+ public let enabled: Bool?
94
+ /// Usage in cents (e.g., 2000 = $20.00)
95
+ public let used: Int?
96
+ /// Limit in cents (e.g., 2000 = $20.00)
97
+ public let limit: Int?
98
+ /// Remaining in cents
99
+ public let remaining: Int?
100
+ public let breakdown: CursorPlanBreakdown?
101
+ public let autoPercentUsed: Double?
102
+ public let apiPercentUsed: Double?
103
+ public let totalPercentUsed: Double?
104
+ }
105
+
106
+ public struct CursorPlanBreakdown: Codable, Sendable {
107
+ public let included: Int?
108
+ public let bonus: Int?
109
+ public let total: Int?
110
+ }
111
+
112
+ public struct CursorOnDemandUsage: Codable, Sendable {
113
+ public let enabled: Bool?
114
+ /// Usage in cents
115
+ public let used: Int?
116
+ /// Limit in cents (nil if unlimited)
117
+ public let limit: Int?
118
+ /// Remaining in cents (nil if unlimited)
119
+ public let remaining: Int?
120
+ }
121
+
122
+ public struct CursorTeamUsage: Codable, Sendable {
123
+ public let onDemand: CursorOnDemandUsage?
124
+ }
125
+
126
+ public struct CursorUserInfo: Codable, Sendable {
127
+ public let email: String?
128
+ public let emailVerified: Bool?
129
+ public let name: String?
130
+ public let sub: String?
131
+ public let createdAt: String?
132
+ public let updatedAt: String?
133
+ public let picture: String?
134
+
135
+ enum CodingKeys: String, CodingKey {
136
+ case email
137
+ case emailVerified = "email_verified"
138
+ case name
139
+ case sub
140
+ case createdAt = "created_at"
141
+ case updatedAt = "updated_at"
142
+ case picture
143
+ }
144
+ }
145
+
146
+ // MARK: - Cursor Status Snapshot
147
+
148
+ public struct CursorStatusSnapshot: Sendable {
149
+ /// Percentage of included plan usage (0-100)
150
+ public let planPercentUsed: Double
151
+ /// Included plan usage in USD
152
+ public let planUsedUSD: Double
153
+ /// Included plan limit in USD
154
+ public let planLimitUSD: Double
155
+ /// On-demand usage in USD
156
+ public let onDemandUsedUSD: Double
157
+ /// On-demand limit in USD (nil if unlimited)
158
+ public let onDemandLimitUSD: Double?
159
+ /// Team on-demand usage in USD (for team plans)
160
+ public let teamOnDemandUsedUSD: Double?
161
+ /// Team on-demand limit in USD
162
+ public let teamOnDemandLimitUSD: Double?
163
+ /// Billing cycle reset date
164
+ public let billingCycleEnd: Date?
165
+ /// Membership type (e.g., "enterprise", "pro", "hobby")
166
+ public let membershipType: String?
167
+ /// User email
168
+ public let accountEmail: String?
169
+ /// User name
170
+ public let accountName: String?
171
+ /// Raw API response for debugging
172
+ public let rawJSON: String?
173
+
174
+ public init(
175
+ planPercentUsed: Double,
176
+ planUsedUSD: Double,
177
+ planLimitUSD: Double,
178
+ onDemandUsedUSD: Double,
179
+ onDemandLimitUSD: Double?,
180
+ teamOnDemandUsedUSD: Double?,
181
+ teamOnDemandLimitUSD: Double?,
182
+ billingCycleEnd: Date?,
183
+ membershipType: String?,
184
+ accountEmail: String?,
185
+ accountName: String?,
186
+ rawJSON: String?)
187
+ {
188
+ self.planPercentUsed = planPercentUsed
189
+ self.planUsedUSD = planUsedUSD
190
+ self.planLimitUSD = planLimitUSD
191
+ self.onDemandUsedUSD = onDemandUsedUSD
192
+ self.onDemandLimitUSD = onDemandLimitUSD
193
+ self.teamOnDemandUsedUSD = teamOnDemandUsedUSD
194
+ self.teamOnDemandLimitUSD = teamOnDemandLimitUSD
195
+ self.billingCycleEnd = billingCycleEnd
196
+ self.membershipType = membershipType
197
+ self.accountEmail = accountEmail
198
+ self.accountName = accountName
199
+ self.rawJSON = rawJSON
200
+ }
201
+
202
+ /// Convert to UsageSnapshot for the common provider interface
203
+ public func toUsageSnapshot() -> UsageSnapshot {
204
+ // Primary: Plan usage percentage
205
+ let primary = RateWindow(
206
+ usedPercent: self.planPercentUsed,
207
+ windowMinutes: nil,
208
+ resetsAt: self.billingCycleEnd,
209
+ resetDescription: self.billingCycleEnd.map { Self.formatResetDate($0) })
210
+
211
+ let resolvedOnDemandUsed: Double
212
+ let resolvedOnDemandLimit: Double?
213
+
214
+ if let teamLimit = self.teamOnDemandLimitUSD,
215
+ let teamUsed = self.teamOnDemandUsedUSD
216
+ {
217
+ resolvedOnDemandUsed = teamUsed
218
+ resolvedOnDemandLimit = teamLimit
219
+ } else {
220
+ resolvedOnDemandUsed = self.onDemandUsedUSD
221
+ resolvedOnDemandLimit = self.onDemandLimitUSD
222
+ }
223
+
224
+ // Secondary: On-demand usage as percentage of limit (team when present, else individual)
225
+ let secondary: RateWindow? = if let limit = resolvedOnDemandLimit,
226
+ limit > 0
227
+ {
228
+ RateWindow(
229
+ usedPercent: (resolvedOnDemandUsed / limit) * 100,
230
+ windowMinutes: nil,
231
+ resetsAt: self.billingCycleEnd,
232
+ resetDescription: self.billingCycleEnd.map { Self.formatResetDate($0) })
233
+ } else {
234
+ nil
235
+ }
236
+
237
+ // Provider cost snapshot for on-demand usage
238
+ let providerCost: ProviderCostSnapshot? = if resolvedOnDemandUsed > 0 {
239
+ ProviderCostSnapshot(
240
+ used: resolvedOnDemandUsed,
241
+ limit: resolvedOnDemandLimit ?? 0,
242
+ currencyCode: "USD",
243
+ period: "monthly",
244
+ resetsAt: self.billingCycleEnd,
245
+ updatedAt: Date())
246
+ } else {
247
+ nil
248
+ }
249
+
250
+ let identity = ProviderIdentitySnapshot(
251
+ providerID: .cursor,
252
+ accountEmail: self.accountEmail,
253
+ accountOrganization: nil,
254
+ loginMethod: self.membershipType.map { Self.formatMembershipType($0) })
255
+ return UsageSnapshot(
256
+ primary: primary,
257
+ secondary: secondary,
258
+ tertiary: nil,
259
+ providerCost: providerCost,
260
+ updatedAt: Date(),
261
+ identity: identity)
262
+ }
263
+
264
+ private static func formatResetDate(_ date: Date) -> String {
265
+ let formatter = DateFormatter()
266
+ formatter.dateFormat = "MMM d 'at' h:mma"
267
+ formatter.locale = Locale(identifier: "en_US_POSIX")
268
+ return "Resets " + formatter.string(from: date)
269
+ }
270
+
271
+ private static func formatMembershipType(_ type: String) -> String {
272
+ switch type.lowercased() {
273
+ case "enterprise":
274
+ "Cursor Enterprise"
275
+ case "pro":
276
+ "Cursor Pro"
277
+ case "hobby":
278
+ "Cursor Hobby"
279
+ case "team":
280
+ "Cursor Team"
281
+ default:
282
+ "Cursor \(type.capitalized)"
283
+ }
284
+ }
285
+ }
286
+
287
+ // MARK: - Cursor Status Probe Error
288
+
289
+ public enum CursorStatusProbeError: LocalizedError, Sendable {
290
+ case notLoggedIn
291
+ case networkError(String)
292
+ case parseFailed(String)
293
+ case noSessionCookie
294
+
295
+ public var errorDescription: String? {
296
+ switch self {
297
+ case .notLoggedIn:
298
+ "Not logged in to Cursor. Please log in via the Runic menu."
299
+ case let .networkError(msg):
300
+ "Cursor API error: \(msg)"
301
+ case let .parseFailed(msg):
302
+ "Could not parse Cursor usage: \(msg)"
303
+ case .noSessionCookie:
304
+ "No Cursor session found. Please log in to cursor.com in \(cursorCookieImportOrder.loginHint)."
305
+ }
306
+ }
307
+ }
308
+
309
+ // MARK: - Cursor Session Store
310
+
311
+ public actor CursorSessionStore {
312
+ public static let shared = CursorSessionStore()
313
+
314
+ private var sessionCookies: [HTTPCookie] = []
315
+ private let fileURL: URL
316
+
317
+ private init() {
318
+ let fm = FileManager.default
319
+ let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
320
+ ?? fm.temporaryDirectory
321
+ let dir = appSupport.appendingPathComponent("Runic", isDirectory: true)
322
+ try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
323
+ self.fileURL = dir.appendingPathComponent("cursor-session.json")
324
+
325
+ // Load saved cookies on init
326
+ Task { await self.loadFromDisk() }
327
+ }
328
+
329
+ public func setCookies(_ cookies: [HTTPCookie]) {
330
+ self.sessionCookies = cookies
331
+ self.saveToDisk()
332
+ }
333
+
334
+ public func getCookies() -> [HTTPCookie] {
335
+ self.sessionCookies
336
+ }
337
+
338
+ public func clearCookies() {
339
+ self.sessionCookies = []
340
+ try? FileManager.default.removeItem(at: self.fileURL)
341
+ }
342
+
343
+ public func hasValidSession() -> Bool {
344
+ !self.sessionCookies.isEmpty
345
+ }
346
+
347
+ private func saveToDisk() {
348
+ // Convert cookie properties to JSON-serializable format
349
+ // Date values must be converted to TimeInterval (Double)
350
+ let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in
351
+ guard let props = cookie.properties else { return nil }
352
+ var serializable: [String: Any] = [:]
353
+ for (key, value) in props {
354
+ let keyString = key.rawValue
355
+ if let date = value as? Date {
356
+ // Convert Date to TimeInterval for JSON compatibility
357
+ serializable[keyString] = date.timeIntervalSince1970
358
+ serializable[keyString + "_isDate"] = true
359
+ } else if let url = value as? URL {
360
+ serializable[keyString] = url.absoluteString
361
+ serializable[keyString + "_isURL"] = true
362
+ } else if JSONSerialization.isValidJSONObject([value]) ||
363
+ value is String ||
364
+ value is Bool ||
365
+ value is NSNumber
366
+ {
367
+ serializable[keyString] = value
368
+ }
369
+ }
370
+ return serializable
371
+ }
372
+ guard !cookieData.isEmpty,
373
+ let data = try? JSONSerialization.data(withJSONObject: cookieData, options: [.prettyPrinted])
374
+ else {
375
+ return
376
+ }
377
+ try? data.write(to: self.fileURL)
378
+ }
379
+
380
+ private func loadFromDisk() {
381
+ guard let data = try? Data(contentsOf: self.fileURL),
382
+ let cookieArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]
383
+ else { return }
384
+
385
+ self.sessionCookies = cookieArray.compactMap { props in
386
+ // Convert back to HTTPCookiePropertyKey dictionary
387
+ var cookieProps: [HTTPCookiePropertyKey: Any] = [:]
388
+ for (key, value) in props {
389
+ // Skip marker keys
390
+ if key.hasSuffix("_isDate") || key.hasSuffix("_isURL") { continue }
391
+
392
+ let propKey = HTTPCookiePropertyKey(key)
393
+
394
+ // Check if this was a Date
395
+ if props[key + "_isDate"] as? Bool == true, let interval = value as? TimeInterval {
396
+ cookieProps[propKey] = Date(timeIntervalSince1970: interval)
397
+ }
398
+ // Check if this was a URL
399
+ else if props[key + "_isURL"] as? Bool == true, let urlString = value as? String {
400
+ cookieProps[propKey] = URL(string: urlString)
401
+ } else {
402
+ cookieProps[propKey] = value
403
+ }
404
+ }
405
+ return HTTPCookie(properties: cookieProps)
406
+ }
407
+ }
408
+ }
409
+
410
+ // MARK: - Cursor Status Probe
411
+
412
+ public struct CursorStatusProbe: Sendable {
413
+ public let baseURL: URL
414
+ public var timeout: TimeInterval = 15.0
415
+
416
+ public init(baseURL: URL = URL(string: "https://cursor.com")!, timeout: TimeInterval = 15.0) {
417
+ self.baseURL = baseURL
418
+ self.timeout = timeout
419
+ }
420
+
421
+ /// Fetch Cursor usage using browser cookies with fallback to stored session.
422
+ public func fetch(logger: ((String) -> Void)? = nil) async throws -> CursorStatusSnapshot {
423
+ let log: (String) -> Void = { msg in logger?("[cursor] \(msg)") }
424
+
425
+ // Try importing cookies from the configured browser order first.
426
+ do {
427
+ let session = try CursorCookieImporter.importSession(logger: log)
428
+ log("Using cookies from \(session.sourceLabel)")
429
+ return try await self.fetchWithCookieHeader(session.cookieHeader)
430
+ } catch {
431
+ log("Browser cookie import failed: \(error.localizedDescription)")
432
+ }
433
+
434
+ // Fall back to stored session cookies (from "Add Account" login flow)
435
+ let storedCookies = await CursorSessionStore.shared.getCookies()
436
+ if !storedCookies.isEmpty {
437
+ log("Using stored session cookies")
438
+ let cookieHeader = storedCookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
439
+ do {
440
+ return try await self.fetchWithCookieHeader(cookieHeader)
441
+ } catch {
442
+ if case CursorStatusProbeError.notLoggedIn = error {
443
+ // Clear only when auth is invalid; keep for transient failures.
444
+ await CursorSessionStore.shared.clearCookies()
445
+ log("Stored session invalid, cleared")
446
+ } else {
447
+ log("Stored session failed: \(error.localizedDescription)")
448
+ }
449
+ }
450
+ }
451
+
452
+ throw CursorStatusProbeError.noSessionCookie
453
+ }
454
+
455
+ private func fetchWithCookieHeader(_ cookieHeader: String) async throws -> CursorStatusSnapshot {
456
+ async let usageSummaryTask = self.fetchUsageSummary(cookieHeader: cookieHeader)
457
+ async let userInfoTask = self.fetchUserInfo(cookieHeader: cookieHeader)
458
+
459
+ let usageSummary = try await usageSummaryTask
460
+ let userInfo = try? await userInfoTask
461
+
462
+ return self.parseUsageSummary(usageSummary, userInfo: userInfo)
463
+ }
464
+
465
+ private func fetchUsageSummary(cookieHeader: String) async throws -> CursorUsageSummary {
466
+ let url = self.baseURL.appendingPathComponent("/api/usage-summary")
467
+ var request = URLRequest(url: url)
468
+ request.timeoutInterval = self.timeout
469
+ request.setValue("application/json", forHTTPHeaderField: "Accept")
470
+ request.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
471
+
472
+ let (data, response) = try await URLSession.shared.data(for: request)
473
+
474
+ guard let httpResponse = response as? HTTPURLResponse else {
475
+ throw CursorStatusProbeError.networkError("Invalid response")
476
+ }
477
+
478
+ if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {
479
+ throw CursorStatusProbeError.notLoggedIn
480
+ }
481
+
482
+ guard httpResponse.statusCode == 200 else {
483
+ throw CursorStatusProbeError.networkError("HTTP \(httpResponse.statusCode)")
484
+ }
485
+
486
+ do {
487
+ let decoder = JSONDecoder()
488
+ return try decoder.decode(CursorUsageSummary.self, from: data)
489
+ } catch {
490
+ let rawJSON = String(data: data, encoding: .utf8) ?? "<binary>"
491
+ throw CursorStatusProbeError
492
+ .parseFailed("JSON decode failed: \(error.localizedDescription). Raw: \(rawJSON.prefix(200))")
493
+ }
494
+ }
495
+
496
+ private func fetchUserInfo(cookieHeader: String) async throws -> CursorUserInfo {
497
+ let url = self.baseURL.appendingPathComponent("/api/auth/me")
498
+ var request = URLRequest(url: url)
499
+ request.timeoutInterval = self.timeout
500
+ request.setValue("application/json", forHTTPHeaderField: "Accept")
501
+ request.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
502
+
503
+ let (data, response) = try await URLSession.shared.data(for: request)
504
+
505
+ guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
506
+ throw CursorStatusProbeError.networkError("Failed to fetch user info")
507
+ }
508
+
509
+ let decoder = JSONDecoder()
510
+ return try decoder.decode(CursorUserInfo.self, from: data)
511
+ }
512
+
513
+ func parseUsageSummary(_ summary: CursorUsageSummary, userInfo: CursorUserInfo?) -> CursorStatusSnapshot {
514
+ // Parse billing cycle end date
515
+ let billingCycleEnd: Date? = summary.billingCycleEnd.flatMap { dateString in
516
+ let formatter = ISO8601DateFormatter()
517
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
518
+ return formatter.date(from: dateString) ?? ISO8601DateFormatter().date(from: dateString)
519
+ }
520
+
521
+ // Convert cents to USD (plan percent derives from raw values to avoid percent unit mismatches).
522
+ let planUsedRaw = Double(summary.individualUsage?.plan?.used ?? 0)
523
+ let planLimitRaw = Double(summary.individualUsage?.plan?.limit ?? 0)
524
+ let planUsed = planUsedRaw / 100.0
525
+ let planLimit = planLimitRaw / 100.0
526
+ let planPercentUsed: Double = if planLimitRaw > 0 {
527
+ (planUsedRaw / planLimitRaw) * 100
528
+ } else if let totalPercentUsed = summary.individualUsage?.plan?.totalPercentUsed {
529
+ totalPercentUsed <= 1 ? totalPercentUsed * 100 : totalPercentUsed
530
+ } else {
531
+ 0
532
+ }
533
+
534
+ let onDemandUsed = Double(summary.individualUsage?.onDemand?.used ?? 0) / 100.0
535
+ let onDemandLimit: Double? = summary.individualUsage?.onDemand?.limit.map { Double($0) / 100.0 }
536
+
537
+ let teamOnDemandUsed: Double? = summary.teamUsage?.onDemand?.used.map { Double($0) / 100.0 }
538
+ let teamOnDemandLimit: Double? = summary.teamUsage?.onDemand?.limit.map { Double($0) / 100.0 }
539
+
540
+ return CursorStatusSnapshot(
541
+ planPercentUsed: planPercentUsed,
542
+ planUsedUSD: planUsed,
543
+ planLimitUSD: planLimit,
544
+ onDemandUsedUSD: onDemandUsed,
545
+ onDemandLimitUSD: onDemandLimit,
546
+ teamOnDemandUsedUSD: teamOnDemandUsed,
547
+ teamOnDemandLimitUSD: teamOnDemandLimit,
548
+ billingCycleEnd: billingCycleEnd,
549
+ membershipType: summary.membershipType,
550
+ accountEmail: userInfo?.email,
551
+ accountName: userInfo?.name,
552
+ rawJSON: nil)
553
+ }
554
+ }
555
+
556
+ #else
557
+
558
+ // MARK: - Cursor (Unsupported)
559
+
560
+ public enum CursorStatusProbeError: LocalizedError, Sendable {
561
+ case notSupported
562
+
563
+ public var errorDescription: String? {
564
+ "Cursor is only supported on macOS."
565
+ }
566
+ }
567
+
568
+ public struct CursorStatusSnapshot: Sendable {
569
+ public init() {}
570
+
571
+ public func toUsageSnapshot() -> UsageSnapshot {
572
+ UsageSnapshot(
573
+ primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
574
+ secondary: nil,
575
+ tertiary: nil,
576
+ providerCost: nil,
577
+ updatedAt: Date(),
578
+ identity: nil)
579
+ }
580
+ }
581
+
582
+ public struct CursorStatusProbe: Sendable {
583
+ public init(baseURL: URL = URL(string: "https://cursor.com")!, timeout: TimeInterval = 15.0) {
584
+ _ = baseURL
585
+ _ = timeout
586
+ }
587
+
588
+ public func fetch(logger: ((String) -> Void)? = nil) async throws -> CursorStatusSnapshot {
589
+ _ = logger
590
+ throw CursorStatusProbeError.notSupported
591
+ }
592
+ }
593
+
594
+ #endif