@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,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol XAITokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum XAITokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainXAITokenStore: XAITokenStoring {
25
+ private static let log = RunicLog.logger("xai-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "xai-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating xAI token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw XAITokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw XAITokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw XAITokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw XAITokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }
@@ -0,0 +1,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol ZaiTokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum ZaiTokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainZaiTokenStore: ZaiTokenStoring {
25
+ private static let log = RunicLog.logger("zai-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "zai-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating z.ai token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw ZaiTokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw ZaiTokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw ZaiTokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw ZaiTokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }
@@ -0,0 +1,193 @@
1
+ import Foundation
2
+ import RunicCore
3
+
4
+ /// Exports usage data from the ledger as CSV or JSON.
5
+ @MainActor
6
+ enum UsageExporter {
7
+ enum Format: String {
8
+ case csv
9
+ case json
10
+ }
11
+
12
+ /// Build an export string for the given provider in the requested format.
13
+ static func export(store: UsageStore, provider: UsageProvider, format: Format) -> String {
14
+ switch format {
15
+ case .csv:
16
+ self.exportCSV(store: store, provider: provider)
17
+ case .json:
18
+ self.exportJSON(store: store, provider: provider)
19
+ }
20
+ }
21
+
22
+ // MARK: - CSV
23
+
24
+ private static func exportCSV(store: UsageStore, provider: UsageProvider) -> String {
25
+ var lines: [String] = []
26
+
27
+ // Header
28
+ lines.append("date,input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens,total_tokens,cost_usd,models_used")
29
+
30
+ // Daily summaries from token snapshot
31
+ let daily = store.tokenSnapshot(for: provider)?.daily ?? []
32
+
33
+ for day in daily {
34
+ let costStr = day.costUSD.map { String(format: "%.4f", $0) } ?? ""
35
+ let modelsStr = Self.csvEscape((day.modelsUsed ?? []).joined(separator: "; "))
36
+ let input = day.inputTokens ?? 0
37
+ let output = day.outputTokens ?? 0
38
+ let cacheCreate = day.cacheCreationTokens ?? 0
39
+ let cacheRead = day.cacheReadTokens ?? 0
40
+ let total = day.totalTokens ?? (input + output + cacheCreate + cacheRead)
41
+ lines.append("\(day.date),\(input),\(output),\(cacheCreate),\(cacheRead),\(total),\(costStr),\(modelsStr)")
42
+ }
43
+
44
+ // If no token snapshot daily data, fall back to ledger daily summary
45
+ if daily.isEmpty, let summary = store.ledgerDailySummary(for: provider) {
46
+ let costStr = summary.totals.costUSD.map { String(format: "%.4f", $0) } ?? ""
47
+ let modelsStr = Self.csvEscape(summary.modelsUsed.joined(separator: "; "))
48
+ lines.append("\(summary.dayKey),\(summary.totals.inputTokens),\(summary.totals.outputTokens),\(summary.totals.cacheCreationTokens),\(summary.totals.cacheReadTokens),\(summary.totals.totalTokens),\(costStr),\(modelsStr)")
49
+ }
50
+
51
+ return lines.joined(separator: "\n")
52
+ }
53
+
54
+ // MARK: - JSON
55
+
56
+ private static func exportJSON(store: UsageStore, provider: UsageProvider) -> String {
57
+ let metadata = store.metadata(for: provider)
58
+ let dateFormatter = DateFormatter()
59
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
60
+ dateFormatter.timeZone = TimeZone.current
61
+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
62
+
63
+ let nowStr = dateFormatter.string(from: Date())
64
+
65
+ var root: [String: Any] = [
66
+ "provider": provider.rawValue,
67
+ "providerDisplayName": metadata.displayName,
68
+ "exportedAt": nowStr,
69
+ ]
70
+
71
+ // Daily summaries from token snapshot
72
+ var dailyArray: [[String: Any]] = []
73
+ let daily = store.tokenSnapshot(for: provider)?.daily ?? []
74
+
75
+ for day in daily {
76
+ let input = day.inputTokens ?? 0
77
+ let output = day.outputTokens ?? 0
78
+ let cacheCreate = day.cacheCreationTokens ?? 0
79
+ let cacheRead = day.cacheReadTokens ?? 0
80
+ let total = day.totalTokens ?? (input + output + cacheCreate + cacheRead)
81
+ var entry: [String: Any] = [
82
+ "date": day.date,
83
+ "inputTokens": input,
84
+ "outputTokens": output,
85
+ "cacheCreationTokens": cacheCreate,
86
+ "cacheReadTokens": cacheRead,
87
+ "totalTokens": total,
88
+ "models": day.modelsUsed ?? [],
89
+ ]
90
+ if let cost = day.costUSD {
91
+ entry["costUSD"] = cost
92
+ }
93
+ dailyArray.append(entry)
94
+ }
95
+
96
+ // Fallback to ledger summary if no token snapshot data
97
+ if dailyArray.isEmpty, let summary = store.ledgerDailySummary(for: provider) {
98
+ var entry: [String: Any] = [
99
+ "date": summary.dayKey,
100
+ "inputTokens": summary.totals.inputTokens,
101
+ "outputTokens": summary.totals.outputTokens,
102
+ "cacheCreationTokens": summary.totals.cacheCreationTokens,
103
+ "cacheReadTokens": summary.totals.cacheReadTokens,
104
+ "totalTokens": summary.totals.totalTokens,
105
+ "models": summary.modelsUsed,
106
+ ]
107
+ if let cost = summary.totals.costUSD {
108
+ entry["costUSD"] = cost
109
+ }
110
+ dailyArray.append(entry)
111
+ }
112
+
113
+ root["dailySummaries"] = dailyArray
114
+
115
+ // Model breakdowns
116
+ let modelBreakdowns = store.ledgerModelBreakdown(for: provider)
117
+ if !modelBreakdowns.isEmpty {
118
+ root["modelBreakdowns"] = modelBreakdowns.map { model -> [String: Any] in
119
+ var entry: [String: Any] = [
120
+ "model": model.model,
121
+ "requestCount": model.entryCount,
122
+ "inputTokens": model.totals.inputTokens,
123
+ "outputTokens": model.totals.outputTokens,
124
+ "totalTokens": model.totals.totalTokens,
125
+ ]
126
+ if let cost = model.totals.costUSD {
127
+ entry["costUSD"] = cost
128
+ }
129
+ if let projectName = model.projectName {
130
+ entry["projectName"] = projectName
131
+ }
132
+ if let projectID = model.projectID {
133
+ entry["projectID"] = projectID
134
+ }
135
+ return entry
136
+ }
137
+ }
138
+
139
+ // Project breakdowns
140
+ let projectBreakdowns = store.ledgerProjectBreakdown(for: provider)
141
+ if !projectBreakdowns.isEmpty {
142
+ root["projectBreakdowns"] = projectBreakdowns.map { project -> [String: Any] in
143
+ var entry: [String: Any] = [
144
+ "projectName": project.displayProjectName,
145
+ "requestCount": project.entryCount,
146
+ "inputTokens": project.totals.inputTokens,
147
+ "outputTokens": project.totals.outputTokens,
148
+ "totalTokens": project.totals.totalTokens,
149
+ "models": project.modelsUsed,
150
+ ]
151
+ if let cost = project.totals.costUSD {
152
+ entry["costUSD"] = cost
153
+ }
154
+ if let projectID = project.projectID {
155
+ entry["projectID"] = projectID
156
+ }
157
+ return entry
158
+ }
159
+ }
160
+
161
+ // Spend forecast
162
+ if let forecast = store.ledgerSpendForecast(for: provider) {
163
+ root["spendForecast"] = [
164
+ "observedDays": forecast.observedDays,
165
+ "observedCostUSD": forecast.observedCostUSD,
166
+ "averageDailyCostUSD": forecast.averageDailyCostUSD,
167
+ "projected30DayCostUSD": forecast.projected30DayCostUSD,
168
+ "budgetWillBreach": forecast.budgetWillBreach,
169
+ ] as [String: Any]
170
+ }
171
+
172
+ guard let data = try? JSONSerialization.data(
173
+ withJSONObject: root,
174
+ options: [.prettyPrinted, .sortedKeys]),
175
+ let string = String(data: data, encoding: .utf8) else
176
+ {
177
+ return "{}"
178
+ }
179
+ return string
180
+ }
181
+
182
+ // MARK: - Helpers
183
+
184
+ /// Escape a value for CSV, quoting if it contains commas, quotes, or newlines.
185
+ private static func csvEscape(_ value: String) -> String {
186
+ let needsQuoting = value.contains(",") || value.contains("\"") || value.contains("\n")
187
+ if needsQuoting {
188
+ let escaped = value.replacingOccurrences(of: "\"", with: "\"\"")
189
+ return "\"\(escaped)\""
190
+ }
191
+ return value
192
+ }
193
+ }
@@ -0,0 +1,21 @@
1
+ import Foundation
2
+
3
+ enum RelativeTimeFormatters {
4
+ @MainActor
5
+ static let full: RelativeDateTimeFormatter = {
6
+ let formatter = RelativeDateTimeFormatter()
7
+ formatter.unitsStyle = .full
8
+ return formatter
9
+ }()
10
+ }
11
+
12
+ extension Date {
13
+ @MainActor
14
+ func relativeDescription(now: Date = .now) -> String {
15
+ let seconds = abs(now.timeIntervalSince(self))
16
+ if seconds < 15 {
17
+ return "just now"
18
+ }
19
+ return RelativeTimeFormatters.full.localizedString(for: self, relativeTo: now)
20
+ }
21
+ }
@@ -0,0 +1,32 @@
1
+ import SwiftUI
2
+
3
+ struct HiddenWindowView: View {
4
+ @Environment(\.openSettings) private var openSettings
5
+
6
+ var body: some View {
7
+ Color.clear
8
+ .frame(width: 20, height: 20)
9
+ .onReceive(NotificationCenter.default.publisher(for: .runicOpenSettings)) { _ in
10
+ Task { @MainActor in
11
+ self.openSettings()
12
+ }
13
+ }
14
+ .onAppear {
15
+ if let window = NSApp.windows.first(where: { $0.title == "RunicLifecycleKeepalive" }) {
16
+ // Make the keepalive window truly invisible and non-interactive.
17
+ window.styleMask = [.borderless]
18
+ window.collectionBehavior = [.auxiliary, .ignoresCycle, .transient, .canJoinAllSpaces]
19
+ window.isExcludedFromWindowsMenu = true
20
+ window.level = .floating
21
+ window.isOpaque = false
22
+ window.alphaValue = 0
23
+ window.backgroundColor = .clear
24
+ window.hasShadow = false
25
+ window.ignoresMouseEvents = true
26
+ window.canHide = false
27
+ window.setContentSize(NSSize(width: 1, height: 1))
28
+ window.setFrameOrigin(NSPoint(x: -5000, y: -5000))
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,9 @@
1
+ import Foundation
2
+
3
+ enum InstallOrigin {
4
+ static func isHomebrewCask(appBundleURL: URL) -> Bool {
5
+ let resolved = appBundleURL.resolvingSymlinksInPath()
6
+ let path = resolved.path
7
+ return path.contains("/Caskroom/") || path.contains("/Homebrew/Caskroom/")
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ import KeyboardShortcuts
2
+
3
+ @MainActor
4
+ extension KeyboardShortcuts.Name {
5
+ static let openMenu = Self("openMenu")
6
+ }
@@ -0,0 +1,65 @@
1
+ import AppKit
2
+ import SwiftUI
3
+
4
+ /// Lightweight NSView-based mouse tracking with local coordinates.
5
+ ///
6
+ /// Why: SwiftUI's `onHover` doesn't provide location, but we want "hover a bar to see values" on macOS.
7
+ @MainActor
8
+ struct MouseLocationReader: NSViewRepresentable {
9
+ let onMoved: (CGPoint?) -> Void
10
+
11
+ func makeNSView(context: Context) -> TrackingView {
12
+ let view = TrackingView()
13
+ view.onMoved = self.onMoved
14
+ return view
15
+ }
16
+
17
+ func updateNSView(_ nsView: TrackingView, context: Context) {
18
+ nsView.onMoved = self.onMoved
19
+ }
20
+
21
+ final class TrackingView: NSView {
22
+ var onMoved: ((CGPoint?) -> Void)?
23
+ private var trackingArea: NSTrackingArea?
24
+
25
+ override func viewDidMoveToWindow() {
26
+ super.viewDidMoveToWindow()
27
+ self.window?.acceptsMouseMovedEvents = true
28
+ self.updateTrackingAreas()
29
+ }
30
+
31
+ override func updateTrackingAreas() {
32
+ super.updateTrackingAreas()
33
+ if let trackingArea {
34
+ self.removeTrackingArea(trackingArea)
35
+ }
36
+
37
+ let options: NSTrackingArea.Options = [
38
+ // NSMenu popups aren't "key windows", so `.activeInKeyWindow` would drop events and cause hover
39
+ // state to flicker. `.activeAlways` keeps tracking stable while the menu is open.
40
+ .activeAlways,
41
+ .inVisibleRect,
42
+ .mouseEnteredAndExited,
43
+ .mouseMoved,
44
+ ]
45
+ let area = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil)
46
+ self.addTrackingArea(area)
47
+ self.trackingArea = area
48
+ }
49
+
50
+ override func mouseEntered(with event: NSEvent) {
51
+ super.mouseEntered(with: event)
52
+ self.onMoved?(self.convert(event.locationInWindow, from: nil))
53
+ }
54
+
55
+ override func mouseMoved(with event: NSEvent) {
56
+ super.mouseMoved(with: event)
57
+ self.onMoved?(self.convert(event.locationInWindow, from: nil))
58
+ }
59
+
60
+ override func mouseExited(with event: NSEvent) {
61
+ super.mouseExited(with: event)
62
+ self.onMoved?(nil)
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,6 @@
1
+ import Foundation
2
+
3
+ extension Notification.Name {
4
+ static let runicOpenSettings = Notification.Name("runicOpenSettings")
5
+ static let runicDebugBlinkNow = Notification.Name("runicDebugBlinkNow")
6
+ }