@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,492 @@
1
+ #if os(macOS)
2
+ import CoreGraphics
3
+ import Foundation
4
+ import WebKit
5
+
6
+ @MainActor
7
+ public struct OpenAIDashboardFetcher {
8
+ public enum FetchError: LocalizedError {
9
+ case loginRequired
10
+ case noDashboardData(body: String)
11
+
12
+ public var errorDescription: String? {
13
+ switch self {
14
+ case .loginRequired:
15
+ "OpenAI web access requires login."
16
+ case let .noDashboardData(body):
17
+ "OpenAI dashboard data not found. Body sample: \(body.prefix(200))"
18
+ }
19
+ }
20
+ }
21
+
22
+ private let usageURL = URL(string: "https://chatgpt.com/codex/settings/usage")!
23
+
24
+ public init() {}
25
+
26
+ public nonisolated static func offscreenHostWindowFrame(for visibleFrame: CGRect) -> CGRect {
27
+ let width: CGFloat = min(1200, visibleFrame.width)
28
+ let height: CGFloat = min(1600, visibleFrame.height)
29
+
30
+ // Keep the WebView "visible" for WebKit hydration, but never show it to the user.
31
+ // Place the window almost entirely off-screen; leave only a 1×1 px intersection.
32
+ let sliver: CGFloat = 1
33
+ return CGRect(
34
+ x: visibleFrame.maxX - sliver,
35
+ y: visibleFrame.maxY - sliver,
36
+ width: width,
37
+ height: height)
38
+ }
39
+
40
+ public nonisolated static func offscreenHostAlphaValue() -> CGFloat {
41
+ // Must be > 0 or WebKit can throttle hydration/timers on the Codex usage SPA.
42
+ 0.001
43
+ }
44
+
45
+ public struct ProbeResult: Sendable {
46
+ public let href: String?
47
+ public let loginRequired: Bool
48
+ public let workspacePicker: Bool
49
+ public let cloudflareInterstitial: Bool
50
+ public let signedInEmail: String?
51
+ public let bodyText: String?
52
+
53
+ public init(
54
+ href: String?,
55
+ loginRequired: Bool,
56
+ workspacePicker: Bool,
57
+ cloudflareInterstitial: Bool,
58
+ signedInEmail: String?,
59
+ bodyText: String?)
60
+ {
61
+ self.href = href
62
+ self.loginRequired = loginRequired
63
+ self.workspacePicker = workspacePicker
64
+ self.cloudflareInterstitial = cloudflareInterstitial
65
+ self.signedInEmail = signedInEmail
66
+ self.bodyText = bodyText
67
+ }
68
+ }
69
+
70
+ public func loadLatestDashboard(
71
+ accountEmail: String?,
72
+ logger: ((String) -> Void)? = nil,
73
+ debugDumpHTML: Bool = false,
74
+ timeout: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot
75
+ {
76
+ let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: accountEmail)
77
+ return try await self.loadLatestDashboard(
78
+ websiteDataStore: store,
79
+ logger: logger,
80
+ debugDumpHTML: debugDumpHTML,
81
+ timeout: timeout)
82
+ }
83
+
84
+ public func loadLatestDashboard(
85
+ websiteDataStore: WKWebsiteDataStore,
86
+ logger: ((String) -> Void)? = nil,
87
+ debugDumpHTML: Bool = false,
88
+ timeout: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot
89
+ {
90
+ let lease = try await self.makeWebView(websiteDataStore: websiteDataStore, logger: logger)
91
+ defer { lease.release() }
92
+ let webView = lease.webView
93
+ let log = lease.log
94
+
95
+ let deadline = Date().addingTimeInterval(timeout)
96
+ var lastBody: String?
97
+ var lastHTML: String?
98
+ var lastHref: String?
99
+ var lastFlags: (loginRequired: Bool, workspacePicker: Bool, cloudflare: Bool)?
100
+ var codeReviewFirstSeenAt: Date?
101
+ var anyDashboardSignalAt: Date?
102
+ var creditsHeaderVisibleAt: Date?
103
+ var lastUsageBreakdownDebug: String?
104
+ var lastCreditsPurchaseURL: String?
105
+
106
+ while Date() < deadline {
107
+ let scrape = try await self.scrape(webView: webView)
108
+ lastBody = scrape.bodyText ?? lastBody
109
+ lastHTML = scrape.bodyHTML ?? lastHTML
110
+
111
+ if scrape.href != lastHref
112
+ || lastFlags?.loginRequired != scrape.loginRequired
113
+ || lastFlags?.workspacePicker != scrape.workspacePicker
114
+ || lastFlags?.cloudflare != scrape.cloudflareInterstitial
115
+ {
116
+ lastHref = scrape.href
117
+ lastFlags = (scrape.loginRequired, scrape.workspacePicker, scrape.cloudflareInterstitial)
118
+ let href = scrape.href ?? "nil"
119
+ log(
120
+ "href=\(href) login=\(scrape.loginRequired) " +
121
+ "workspace=\(scrape.workspacePicker) cloudflare=\(scrape.cloudflareInterstitial)")
122
+ }
123
+
124
+ if scrape.workspacePicker {
125
+ try? await Task.sleep(for: .milliseconds(500))
126
+ continue
127
+ }
128
+
129
+ // The page is a SPA and can land on ChatGPT UI or other routes; keep forcing the usage URL.
130
+ if let href = scrape.href, !href.contains("/codex/settings/usage") {
131
+ _ = webView.load(URLRequest(url: self.usageURL))
132
+ try? await Task.sleep(for: .milliseconds(500))
133
+ continue
134
+ }
135
+
136
+ if scrape.loginRequired {
137
+ if debugDumpHTML, let html = scrape.bodyHTML {
138
+ Self.writeDebugArtifacts(html: html, bodyText: scrape.bodyText, logger: log)
139
+ }
140
+ throw FetchError.loginRequired
141
+ }
142
+
143
+ if scrape.cloudflareInterstitial {
144
+ if debugDumpHTML, let html = scrape.bodyHTML {
145
+ Self.writeDebugArtifacts(html: html, bodyText: scrape.bodyText, logger: log)
146
+ }
147
+ throw FetchError.noDashboardData(body: "Cloudflare challenge detected in WebView.")
148
+ }
149
+
150
+ let bodyText = scrape.bodyText ?? ""
151
+ let codeReview = OpenAIDashboardParser.parseCodeReviewRemainingPercent(bodyText: bodyText)
152
+ let events = OpenAIDashboardParser.parseCreditEvents(rows: scrape.rows)
153
+ let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30)
154
+ let usageBreakdown = scrape.usageBreakdown
155
+ let rateLimits = OpenAIDashboardParser.parseRateLimits(bodyText: bodyText)
156
+ let creditsRemaining = OpenAIDashboardParser.parseCreditsRemaining(bodyText: bodyText)
157
+ let accountPlan = scrape.bodyHTML.flatMap(OpenAIDashboardParser.parsePlanFromHTML)
158
+ let hasUsageLimits = rateLimits.primary != nil || rateLimits.secondary != nil
159
+
160
+ if codeReview != nil, codeReviewFirstSeenAt == nil { codeReviewFirstSeenAt = Date() }
161
+ if anyDashboardSignalAt == nil,
162
+ codeReview != nil || !usageBreakdown.isEmpty || scrape.creditsHeaderPresent ||
163
+ hasUsageLimits || creditsRemaining != nil
164
+ {
165
+ anyDashboardSignalAt = Date()
166
+ }
167
+ if codeReview != nil, usageBreakdown.isEmpty,
168
+ let debug = scrape.usageBreakdownDebug, !debug.isEmpty,
169
+ debug != lastUsageBreakdownDebug
170
+ {
171
+ lastUsageBreakdownDebug = debug
172
+ log("usage breakdown debug: \(debug)")
173
+ }
174
+ if let purchaseURL = scrape.creditsPurchaseURL, purchaseURL != lastCreditsPurchaseURL {
175
+ lastCreditsPurchaseURL = purchaseURL
176
+ log("credits purchase url: \(purchaseURL)")
177
+ }
178
+ if events.isEmpty,
179
+ codeReview != nil || !usageBreakdown.isEmpty || hasUsageLimits || creditsRemaining != nil
180
+ {
181
+ log(
182
+ "credits header present=\(scrape.creditsHeaderPresent) " +
183
+ "inViewport=\(scrape.creditsHeaderInViewport) didScroll=\(scrape.didScrollToCredits) " +
184
+ "rows=\(scrape.rows.count)")
185
+ if scrape.didScrollToCredits {
186
+ log("scrollIntoView(Credits usage history) requested; waiting…")
187
+ try? await Task.sleep(for: .milliseconds(600))
188
+ continue
189
+ }
190
+
191
+ // Avoid returning early when the usage breakdown chart hydrates before the (often virtualized)
192
+ // credits table. When we detect a dashboard signal, give credits history a moment to appear.
193
+ if scrape.creditsHeaderPresent, scrape.creditsHeaderInViewport, creditsHeaderVisibleAt == nil {
194
+ creditsHeaderVisibleAt = Date()
195
+ }
196
+ if Self.shouldWaitForCreditsHistory(.init(
197
+ now: Date(),
198
+ anyDashboardSignalAt: anyDashboardSignalAt,
199
+ creditsHeaderVisibleAt: creditsHeaderVisibleAt,
200
+ creditsHeaderPresent: scrape.creditsHeaderPresent,
201
+ creditsHeaderInViewport: scrape.creditsHeaderInViewport,
202
+ didScrollToCredits: scrape.didScrollToCredits))
203
+ {
204
+ try? await Task.sleep(for: .milliseconds(400))
205
+ continue
206
+ }
207
+ }
208
+
209
+ if codeReview != nil || !events.isEmpty || !usageBreakdown
210
+ .isEmpty || hasUsageLimits || creditsRemaining != nil
211
+ {
212
+ // The usage breakdown chart is hydrated asynchronously. When code review is already present,
213
+ // give it a moment to populate so the menu can show it.
214
+ if codeReview != nil, usageBreakdown.isEmpty {
215
+ let elapsed = Date().timeIntervalSince(codeReviewFirstSeenAt ?? Date())
216
+ if elapsed < 6 {
217
+ try? await Task.sleep(for: .milliseconds(400))
218
+ continue
219
+ }
220
+ }
221
+ return OpenAIDashboardSnapshot(
222
+ signedInEmail: scrape.signedInEmail,
223
+ codeReviewRemainingPercent: codeReview,
224
+ creditEvents: events,
225
+ dailyBreakdown: breakdown,
226
+ usageBreakdown: usageBreakdown,
227
+ creditsPurchaseURL: scrape.creditsPurchaseURL,
228
+ primaryLimit: rateLimits.primary,
229
+ secondaryLimit: rateLimits.secondary,
230
+ creditsRemaining: creditsRemaining,
231
+ accountPlan: accountPlan,
232
+ updatedAt: Date())
233
+ }
234
+
235
+ try? await Task.sleep(for: .milliseconds(500))
236
+ }
237
+
238
+ if debugDumpHTML, let html = lastHTML {
239
+ Self.writeDebugArtifacts(html: html, bodyText: lastBody, logger: log)
240
+ }
241
+ throw FetchError.noDashboardData(body: lastBody ?? "")
242
+ }
243
+
244
+ struct CreditsHistoryWaitContext: Sendable {
245
+ let now: Date
246
+ let anyDashboardSignalAt: Date?
247
+ let creditsHeaderVisibleAt: Date?
248
+ let creditsHeaderPresent: Bool
249
+ let creditsHeaderInViewport: Bool
250
+ let didScrollToCredits: Bool
251
+ }
252
+
253
+ nonisolated static func shouldWaitForCreditsHistory(_ context: CreditsHistoryWaitContext) -> Bool {
254
+ if context.didScrollToCredits { return true }
255
+
256
+ // When the header is visible but rows are still empty, wait briefly for the table to render.
257
+ if context.creditsHeaderPresent, context.creditsHeaderInViewport {
258
+ if let creditsHeaderVisibleAt = context.creditsHeaderVisibleAt {
259
+ return context.now.timeIntervalSince(creditsHeaderVisibleAt) < 2.5
260
+ }
261
+ return true
262
+ }
263
+
264
+ // Header not in view yet: allow a short grace period after we first detect any dashboard signal so
265
+ // a scroll (or hydration) can bring the credits section into the DOM.
266
+ if let anyDashboardSignalAt = context.anyDashboardSignalAt {
267
+ return context.now.timeIntervalSince(anyDashboardSignalAt) < 6.5
268
+ }
269
+ return false
270
+ }
271
+
272
+ public func clearSessionData(accountEmail: String?) async {
273
+ let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: accountEmail)
274
+ OpenAIDashboardWebViewCache.shared.evict(websiteDataStore: store)
275
+ await OpenAIDashboardWebsiteDataStore.clearStore(forAccountEmail: accountEmail)
276
+ }
277
+
278
+ public func probeUsagePage(
279
+ websiteDataStore: WKWebsiteDataStore,
280
+ logger: ((String) -> Void)? = nil,
281
+ timeout: TimeInterval = 30) async throws -> ProbeResult
282
+ {
283
+ let lease = try await self.makeWebView(websiteDataStore: websiteDataStore, logger: logger)
284
+ defer { lease.release() }
285
+ let webView = lease.webView
286
+ let log = lease.log
287
+
288
+ let deadline = Date().addingTimeInterval(timeout)
289
+ var lastBody: String?
290
+ var lastHref: String?
291
+
292
+ while Date() < deadline {
293
+ let scrape = try await self.scrape(webView: webView)
294
+ lastBody = scrape.bodyText ?? lastBody
295
+ lastHref = scrape.href ?? lastHref
296
+
297
+ if scrape.workspacePicker {
298
+ try? await Task.sleep(for: .milliseconds(500))
299
+ continue
300
+ }
301
+
302
+ if let href = scrape.href, !href.contains("/codex/settings/usage") {
303
+ _ = webView.load(URLRequest(url: self.usageURL))
304
+ try? await Task.sleep(for: .milliseconds(500))
305
+ continue
306
+ }
307
+
308
+ if scrape.loginRequired { throw FetchError.loginRequired }
309
+ if scrape.cloudflareInterstitial {
310
+ throw FetchError.noDashboardData(body: "Cloudflare challenge detected in WebView.")
311
+ }
312
+
313
+ return ProbeResult(
314
+ href: scrape.href,
315
+ loginRequired: scrape.loginRequired,
316
+ workspacePicker: scrape.workspacePicker,
317
+ cloudflareInterstitial: scrape.cloudflareInterstitial,
318
+ signedInEmail: scrape.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines),
319
+ bodyText: scrape.bodyText)
320
+ }
321
+
322
+ log("Probe timed out (href=\(lastHref ?? "nil"))")
323
+ return ProbeResult(
324
+ href: lastHref,
325
+ loginRequired: false,
326
+ workspacePicker: false,
327
+ cloudflareInterstitial: false,
328
+ signedInEmail: nil,
329
+ bodyText: lastBody)
330
+ }
331
+
332
+ // MARK: - JS scrape
333
+
334
+ private struct ScrapeResult {
335
+ let loginRequired: Bool
336
+ let workspacePicker: Bool
337
+ let cloudflareInterstitial: Bool
338
+ let href: String?
339
+ let bodyText: String?
340
+ let bodyHTML: String?
341
+ let signedInEmail: String?
342
+ let creditsPurchaseURL: String?
343
+ let rows: [[String]]
344
+ let usageBreakdown: [OpenAIDashboardDailyBreakdown]
345
+ let usageBreakdownDebug: String?
346
+ let scrollY: Double
347
+ let scrollHeight: Double
348
+ let viewportHeight: Double
349
+ let creditsHeaderPresent: Bool
350
+ let creditsHeaderInViewport: Bool
351
+ let didScrollToCredits: Bool
352
+ }
353
+
354
+ private func scrape(webView: WKWebView) async throws -> ScrapeResult {
355
+ let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)
356
+ guard let dict = any as? [String: Any] else {
357
+ return ScrapeResult(
358
+ loginRequired: true,
359
+ workspacePicker: false,
360
+ cloudflareInterstitial: false,
361
+ href: nil,
362
+ bodyText: nil,
363
+ bodyHTML: nil,
364
+ signedInEmail: nil,
365
+ creditsPurchaseURL: nil,
366
+ rows: [],
367
+ usageBreakdown: [],
368
+ usageBreakdownDebug: nil,
369
+ scrollY: 0,
370
+ scrollHeight: 0,
371
+ viewportHeight: 0,
372
+ creditsHeaderPresent: false,
373
+ creditsHeaderInViewport: false,
374
+ didScrollToCredits: false)
375
+ }
376
+
377
+ var loginRequired = (dict["loginRequired"] as? Bool) ?? false
378
+ let workspacePicker = (dict["workspacePicker"] as? Bool) ?? false
379
+ let cloudflareInterstitial = (dict["cloudflareInterstitial"] as? Bool) ?? false
380
+ let rows = (dict["rows"] as? [[String]]) ?? []
381
+ let bodyHTML = dict["bodyHTML"] as? String
382
+
383
+ var usageBreakdown: [OpenAIDashboardDailyBreakdown] = []
384
+ let usageBreakdownDebug = dict["usageBreakdownDebug"] as? String
385
+ if let raw = dict["usageBreakdownJSON"] as? String, !raw.isEmpty {
386
+ do {
387
+ let decoder = JSONDecoder()
388
+ usageBreakdown = try decoder.decode([OpenAIDashboardDailyBreakdown].self, from: Data(raw.utf8))
389
+ } catch {
390
+ // Best-effort parse; ignore errors to avoid blocking other dashboard data.
391
+ usageBreakdown = []
392
+ }
393
+ }
394
+
395
+ var signedInEmail = dict["signedInEmail"] as? String
396
+ if let bodyHTML,
397
+ signedInEmail == nil || signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true
398
+ {
399
+ signedInEmail = OpenAIDashboardParser.parseSignedInEmailFromClientBootstrap(html: bodyHTML)
400
+ }
401
+
402
+ if let bodyHTML, let authStatus = OpenAIDashboardParser.parseAuthStatusFromClientBootstrap(html: bodyHTML) {
403
+ if authStatus.lowercased() != "logged_in" {
404
+ // When logged out, the SPA can render a generic landing shell without obvious auth inputs,
405
+ // so treat it as login-required and let the caller retry cookie import.
406
+ loginRequired = true
407
+ }
408
+ }
409
+
410
+ return ScrapeResult(
411
+ loginRequired: loginRequired,
412
+ workspacePicker: workspacePicker,
413
+ cloudflareInterstitial: cloudflareInterstitial,
414
+ href: dict["href"] as? String,
415
+ bodyText: dict["bodyText"] as? String,
416
+ bodyHTML: bodyHTML,
417
+ signedInEmail: signedInEmail,
418
+ creditsPurchaseURL: dict["creditsPurchaseURL"] as? String,
419
+ rows: rows,
420
+ usageBreakdown: usageBreakdown,
421
+ usageBreakdownDebug: usageBreakdownDebug,
422
+ scrollY: (dict["scrollY"] as? NSNumber)?.doubleValue ?? 0,
423
+ scrollHeight: (dict["scrollHeight"] as? NSNumber)?.doubleValue ?? 0,
424
+ viewportHeight: (dict["viewportHeight"] as? NSNumber)?.doubleValue ?? 0,
425
+ creditsHeaderPresent: (dict["creditsHeaderPresent"] as? Bool) ?? false,
426
+ creditsHeaderInViewport: (dict["creditsHeaderInViewport"] as? Bool) ?? false,
427
+ didScrollToCredits: (dict["didScrollToCredits"] as? Bool) ?? false)
428
+ }
429
+
430
+ private func makeWebView(
431
+ websiteDataStore: WKWebsiteDataStore,
432
+ logger: ((String) -> Void)?) async throws -> OpenAIDashboardWebViewLease
433
+ {
434
+ try await OpenAIDashboardWebViewCache.shared.acquire(
435
+ websiteDataStore: websiteDataStore,
436
+ usageURL: self.usageURL,
437
+ logger: logger)
438
+ }
439
+
440
+ private static func writeDebugArtifacts(html: String, bodyText: String?, logger: (String) -> Void) {
441
+ let stamp = Int(Date().timeIntervalSince1970)
442
+ let dir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
443
+ let htmlURL = dir.appendingPathComponent("codex-openai-dashboard-\(stamp).html")
444
+ do {
445
+ try html.write(to: htmlURL, atomically: true, encoding: .utf8)
446
+ logger("Dumped HTML: \(htmlURL.path)")
447
+ } catch {
448
+ logger("Failed to dump HTML: \(error.localizedDescription)")
449
+ }
450
+
451
+ if let bodyText, !bodyText.isEmpty {
452
+ let textURL = dir.appendingPathComponent("codex-openai-dashboard-\(stamp).txt")
453
+ do {
454
+ try bodyText.write(to: textURL, atomically: true, encoding: .utf8)
455
+ logger("Dumped text: \(textURL.path)")
456
+ } catch {
457
+ logger("Failed to dump text: \(error.localizedDescription)")
458
+ }
459
+ }
460
+ }
461
+ }
462
+ #else
463
+ import Foundation
464
+
465
+ @MainActor
466
+ public struct OpenAIDashboardFetcher {
467
+ public enum FetchError: LocalizedError {
468
+ case loginRequired
469
+ case noDashboardData(body: String)
470
+
471
+ public var errorDescription: String? {
472
+ switch self {
473
+ case .loginRequired:
474
+ "OpenAI web access requires login."
475
+ case let .noDashboardData(body):
476
+ "OpenAI dashboard data not found. Body sample: \(body.prefix(200))"
477
+ }
478
+ }
479
+ }
480
+
481
+ public init() {}
482
+
483
+ public func loadLatestDashboard(
484
+ accountEmail _: String?,
485
+ logger _: ((String) -> Void)? = nil,
486
+ debugDumpHTML _: Bool = false,
487
+ timeout _: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot
488
+ {
489
+ throw FetchError.noDashboardData(body: "OpenAI web dashboard fetch is only supported on macOS.")
490
+ }
491
+ }
492
+ #endif
@@ -0,0 +1,51 @@
1
+ #if os(macOS)
2
+ import Foundation
3
+ import WebKit
4
+
5
+ // MARK: - Navigation helper (revived from the old credits scraper)
6
+
7
+ @MainActor
8
+ final class NavigationDelegate: NSObject, WKNavigationDelegate {
9
+ private let completion: (Result<Void, Error>) -> Void
10
+ private var hasCompleted: Bool = false
11
+ static var associationKey: UInt8 = 0
12
+
13
+ init(completion: @escaping (Result<Void, Error>) -> Void) {
14
+ self.completion = completion
15
+ }
16
+
17
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
18
+ self.completeOnce(.success(()))
19
+ }
20
+
21
+ func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
22
+ self.completeOnce(.failure(error))
23
+ }
24
+
25
+ func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
26
+ self.completeOnce(.failure(error))
27
+ }
28
+
29
+ private func completeOnce(_ result: Result<Void, Error>) {
30
+ guard !self.hasCompleted else { return }
31
+ self.hasCompleted = true
32
+ self.completion(result)
33
+ }
34
+ }
35
+
36
+ extension WKWebView {
37
+ var codexNavigationDelegate: NavigationDelegate? {
38
+ get {
39
+ objc_getAssociatedObject(self, &NavigationDelegate.associationKey) as? NavigationDelegate
40
+ }
41
+ set {
42
+ objc_setAssociatedObject(
43
+ self,
44
+ &NavigationDelegate.associationKey,
45
+ newValue,
46
+ .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
47
+ }
48
+ }
49
+ }
50
+
51
+ #endif