@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,694 @@
1
+ import Foundation
2
+ #if canImport(Darwin)
3
+ import Darwin
4
+ #else
5
+ import Glibc
6
+ #endif
7
+
8
+ public struct RateWindow: Codable, Equatable, Sendable {
9
+ public let usedPercent: Double
10
+ public let windowMinutes: Int?
11
+ public let resetsAt: Date?
12
+ /// Optional textual reset description (used by Claude CLI UI scrape).
13
+ public let resetDescription: String?
14
+ /// Optional label for this quota window (for example, a model name).
15
+ public let label: String?
16
+
17
+ public init(
18
+ usedPercent: Double,
19
+ windowMinutes: Int?,
20
+ resetsAt: Date?,
21
+ resetDescription: String?,
22
+ label: String? = nil)
23
+ {
24
+ self.usedPercent = usedPercent
25
+ self.windowMinutes = windowMinutes
26
+ self.resetsAt = resetsAt
27
+ self.resetDescription = resetDescription
28
+ self.label = label
29
+ }
30
+
31
+ public var remainingPercent: Double {
32
+ max(0, 100 - self.usedPercent)
33
+ }
34
+ }
35
+
36
+ public struct ProviderIdentitySnapshot: Codable, Sendable {
37
+ public let providerID: UsageProvider?
38
+ public let accountEmail: String?
39
+ public let accountOrganization: String?
40
+ public let loginMethod: String?
41
+
42
+ public init(
43
+ providerID: UsageProvider?,
44
+ accountEmail: String?,
45
+ accountOrganization: String?,
46
+ loginMethod: String?)
47
+ {
48
+ self.providerID = providerID
49
+ self.accountEmail = accountEmail
50
+ self.accountOrganization = accountOrganization
51
+ self.loginMethod = loginMethod
52
+ }
53
+
54
+ public func scoped(to provider: UsageProvider) -> ProviderIdentitySnapshot {
55
+ if self.providerID == provider { return self }
56
+ return ProviderIdentitySnapshot(
57
+ providerID: provider,
58
+ accountEmail: self.accountEmail,
59
+ accountOrganization: self.accountOrganization,
60
+ loginMethod: self.loginMethod)
61
+ }
62
+ }
63
+
64
+ public struct UsageSnapshot: Codable, Sendable {
65
+ public let primary: RateWindow
66
+ public let secondary: RateWindow?
67
+ public let tertiary: RateWindow?
68
+ public let providerCost: ProviderCostSnapshot?
69
+ public let zaiUsage: ZaiUsageSnapshot?
70
+ public let updatedAt: Date
71
+ public let identity: ProviderIdentitySnapshot?
72
+
73
+ private enum CodingKeys: String, CodingKey {
74
+ case primary
75
+ case secondary
76
+ case tertiary
77
+ case providerCost
78
+ case updatedAt
79
+ case identity
80
+ case accountEmail
81
+ case accountOrganization
82
+ case loginMethod
83
+ }
84
+
85
+ public init(
86
+ primary: RateWindow,
87
+ secondary: RateWindow?,
88
+ tertiary: RateWindow? = nil,
89
+ providerCost: ProviderCostSnapshot? = nil,
90
+ zaiUsage: ZaiUsageSnapshot? = nil,
91
+ updatedAt: Date,
92
+ identity: ProviderIdentitySnapshot? = nil)
93
+ {
94
+ self.primary = primary
95
+ self.secondary = secondary
96
+ self.tertiary = tertiary
97
+ self.providerCost = providerCost
98
+ self.zaiUsage = zaiUsage
99
+ self.updatedAt = updatedAt
100
+ self.identity = identity
101
+ }
102
+
103
+ public init(from decoder: Decoder) throws {
104
+ let container = try decoder.container(keyedBy: CodingKeys.self)
105
+ self.primary = try container.decode(RateWindow.self, forKey: .primary)
106
+ self.secondary = try container.decodeIfPresent(RateWindow.self, forKey: .secondary)
107
+ self.tertiary = try container.decodeIfPresent(RateWindow.self, forKey: .tertiary)
108
+ self.providerCost = try container.decodeIfPresent(ProviderCostSnapshot.self, forKey: .providerCost)
109
+ self.zaiUsage = nil // Not persisted, fetched fresh each time
110
+ self.updatedAt = try container.decode(Date.self, forKey: .updatedAt)
111
+ if let identity = try container.decodeIfPresent(ProviderIdentitySnapshot.self, forKey: .identity) {
112
+ self.identity = identity
113
+ } else {
114
+ let email = try container.decodeIfPresent(String.self, forKey: .accountEmail)
115
+ let organization = try container.decodeIfPresent(String.self, forKey: .accountOrganization)
116
+ let loginMethod = try container.decodeIfPresent(String.self, forKey: .loginMethod)
117
+ if email != nil || organization != nil || loginMethod != nil {
118
+ self.identity = ProviderIdentitySnapshot(
119
+ providerID: nil,
120
+ accountEmail: email,
121
+ accountOrganization: organization,
122
+ loginMethod: loginMethod)
123
+ } else {
124
+ self.identity = nil
125
+ }
126
+ }
127
+ }
128
+
129
+ public func encode(to encoder: Encoder) throws {
130
+ var container = encoder.container(keyedBy: CodingKeys.self)
131
+ try container.encode(self.primary, forKey: .primary)
132
+ if let secondary = self.secondary {
133
+ try container.encode(secondary, forKey: .secondary)
134
+ } else {
135
+ try container.encodeNil(forKey: .secondary)
136
+ }
137
+ try container.encodeIfPresent(self.tertiary, forKey: .tertiary)
138
+ try container.encodeIfPresent(self.providerCost, forKey: .providerCost)
139
+ try container.encode(self.updatedAt, forKey: .updatedAt)
140
+ try container.encodeIfPresent(self.identity, forKey: .identity)
141
+ try container.encodeIfPresent(self.identity?.accountEmail, forKey: .accountEmail)
142
+ try container.encodeIfPresent(self.identity?.accountOrganization, forKey: .accountOrganization)
143
+ try container.encodeIfPresent(self.identity?.loginMethod, forKey: .loginMethod)
144
+ }
145
+
146
+ public func identity(for provider: UsageProvider) -> ProviderIdentitySnapshot? {
147
+ guard let identity, identity.providerID == provider else { return nil }
148
+ return identity
149
+ }
150
+
151
+ public func accountEmail(for provider: UsageProvider) -> String? {
152
+ self.identity(for: provider)?.accountEmail
153
+ }
154
+
155
+ public func accountOrganization(for provider: UsageProvider) -> String? {
156
+ self.identity(for: provider)?.accountOrganization
157
+ }
158
+
159
+ public func loginMethod(for provider: UsageProvider) -> String? {
160
+ self.identity(for: provider)?.loginMethod
161
+ }
162
+
163
+ public func scoped(to provider: UsageProvider) -> UsageSnapshot {
164
+ guard let identity else { return self }
165
+ let scopedIdentity = identity.scoped(to: provider)
166
+ if scopedIdentity.providerID == identity.providerID { return self }
167
+ return UsageSnapshot(
168
+ primary: self.primary,
169
+ secondary: self.secondary,
170
+ tertiary: self.tertiary,
171
+ providerCost: self.providerCost,
172
+ zaiUsage: self.zaiUsage,
173
+ updatedAt: self.updatedAt,
174
+ identity: scopedIdentity)
175
+ }
176
+ }
177
+
178
+ public struct AccountInfo: Equatable, Sendable {
179
+ public let email: String?
180
+ public let plan: String?
181
+
182
+ public init(email: String?, plan: String?) {
183
+ self.email = email
184
+ self.plan = plan
185
+ }
186
+ }
187
+
188
+ public enum UsageError: LocalizedError, Sendable {
189
+ case noSessions
190
+ case noRateLimitsFound
191
+ case decodeFailed
192
+
193
+ public var errorDescription: String? {
194
+ switch self {
195
+ case .noSessions:
196
+ "No Codex sessions found yet. Run at least one Codex prompt first."
197
+ case .noRateLimitsFound:
198
+ "Found sessions, but no rate limit events yet."
199
+ case .decodeFailed:
200
+ "Could not parse Codex session log."
201
+ }
202
+ }
203
+ }
204
+
205
+ // MARK: - Codex RPC client (local process)
206
+
207
+ private struct RPCAccountResponse: Decodable {
208
+ let account: RPCAccountDetails?
209
+ let requiresOpenaiAuth: Bool?
210
+ }
211
+
212
+ private enum RPCAccountDetails: Decodable {
213
+ case apiKey
214
+ case chatgpt(email: String, planType: String)
215
+
216
+ enum CodingKeys: String, CodingKey {
217
+ case type
218
+ case email
219
+ case planType
220
+ }
221
+
222
+ init(from decoder: Decoder) throws {
223
+ let container = try decoder.container(keyedBy: CodingKeys.self)
224
+ let type = try container.decode(String.self, forKey: .type)
225
+ switch type.lowercased() {
226
+ case "apikey":
227
+ self = .apiKey
228
+ case "chatgpt":
229
+ let email = try container.decodeIfPresent(String.self, forKey: .email) ?? "unknown"
230
+ let plan = try container.decodeIfPresent(String.self, forKey: .planType) ?? "unknown"
231
+ self = .chatgpt(email: email, planType: plan)
232
+ default:
233
+ throw DecodingError.dataCorruptedError(
234
+ forKey: .type,
235
+ in: container,
236
+ debugDescription: "Unknown account type \(type)")
237
+ }
238
+ }
239
+ }
240
+
241
+ private struct RPCRateLimitsResponse: Decodable, Encodable {
242
+ let rateLimits: RPCRateLimitSnapshot
243
+ }
244
+
245
+ private struct RPCRateLimitSnapshot: Decodable, Encodable {
246
+ let primary: RPCRateLimitWindow?
247
+ let secondary: RPCRateLimitWindow?
248
+ let credits: RPCCreditsSnapshot?
249
+ }
250
+
251
+ private struct RPCRateLimitWindow: Decodable, Encodable {
252
+ let usedPercent: Double
253
+ let windowDurationMins: Int?
254
+ let resetsAt: Int?
255
+ }
256
+
257
+ private struct RPCCreditsSnapshot: Decodable, Encodable {
258
+ let hasCredits: Bool
259
+ let unlimited: Bool
260
+ let balance: String?
261
+ }
262
+
263
+ private enum RPCWireError: Error, CustomStringConvertible {
264
+ case startFailed(String)
265
+ case requestFailed(String)
266
+ case malformed(String)
267
+
268
+ var description: String {
269
+ switch self {
270
+ case let .startFailed(message):
271
+ "Failed to start codex app-server: \(message)"
272
+ case let .requestFailed(message):
273
+ "RPC request failed: \(message)"
274
+ case let .malformed(message):
275
+ "Malformed response: \(message)"
276
+ }
277
+ }
278
+ }
279
+
280
+ // RPC helper used on background tasks; safe because we confine it to the owning task.
281
+ private final class CodexRPCClient: @unchecked Sendable {
282
+ private let process = Process()
283
+ private let stdinPipe = Pipe()
284
+ private let stdoutPipe = Pipe()
285
+ private let stderrPipe = Pipe()
286
+ private let stdoutLineStream: AsyncStream<Data>
287
+ private let stdoutLineContinuation: AsyncStream<Data>.Continuation
288
+ private var nextID = 1
289
+ private var processGroup: pid_t?
290
+
291
+ private final class LineBuffer: @unchecked Sendable {
292
+ private let lock = NSLock()
293
+ private var buffer = Data()
294
+
295
+ func appendAndDrainLines(_ data: Data) -> [Data] {
296
+ self.lock.lock()
297
+ defer { self.lock.unlock() }
298
+
299
+ self.buffer.append(data)
300
+ var out: [Data] = []
301
+ while let newline = self.buffer.firstIndex(of: 0x0A) {
302
+ let lineData = Data(self.buffer[..<newline])
303
+ self.buffer.removeSubrange(...newline)
304
+ if !lineData.isEmpty {
305
+ out.append(lineData)
306
+ }
307
+ }
308
+ return out
309
+ }
310
+ }
311
+
312
+ private static func debugWriteStderr(_ message: String) {
313
+ #if !os(Linux)
314
+ fputs(message, stderr)
315
+ #endif
316
+ }
317
+
318
+ init(
319
+ executable: String = "codex",
320
+ arguments: [String] = ["-s", "read-only", "-a", "untrusted", "app-server"]) throws
321
+ {
322
+ var stdoutContinuation: AsyncStream<Data>.Continuation!
323
+ self.stdoutLineStream = AsyncStream<Data> { continuation in
324
+ stdoutContinuation = continuation
325
+ }
326
+ self.stdoutLineContinuation = stdoutContinuation
327
+
328
+ let resolvedExec = BinaryLocator.resolveCodexBinary()
329
+ ?? TTYCommandRunner.which(executable)
330
+
331
+ guard let resolvedExec else {
332
+ throw RPCWireError.startFailed(
333
+ "Codex CLI not found. Install with `npm i -g @openai/codex` (or bun) then relaunch Runic.")
334
+ }
335
+ var env = ProcessInfo.processInfo.environment
336
+ env["PATH"] = PathBuilder.effectivePATH(
337
+ purposes: [.rpc, .nodeTooling],
338
+ env: env)
339
+
340
+ self.process.environment = env
341
+ self.process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
342
+ self.process.arguments = [resolvedExec] + arguments
343
+ self.process.standardInput = self.stdinPipe
344
+ self.process.standardOutput = self.stdoutPipe
345
+ self.process.standardError = self.stderrPipe
346
+
347
+ do {
348
+ try self.process.run()
349
+ } catch {
350
+ throw RPCWireError.startFailed(error.localizedDescription)
351
+ }
352
+
353
+ let pid = self.process.processIdentifier
354
+ if setpgid(pid, pid) == 0 {
355
+ self.processGroup = pid
356
+ }
357
+
358
+ let stdoutHandle = self.stdoutPipe.fileHandleForReading
359
+ let stdoutLineContinuation = self.stdoutLineContinuation
360
+ let stdoutBuffer = LineBuffer()
361
+ stdoutHandle.readabilityHandler = { handle in
362
+ let data = handle.availableData
363
+ if data.isEmpty {
364
+ handle.readabilityHandler = nil
365
+ stdoutLineContinuation.finish()
366
+ return
367
+ }
368
+
369
+ let lines = stdoutBuffer.appendAndDrainLines(data)
370
+
371
+ for lineData in lines {
372
+ stdoutLineContinuation.yield(lineData)
373
+ }
374
+ }
375
+
376
+ let stderrHandle = self.stderrPipe.fileHandleForReading
377
+ stderrHandle.readabilityHandler = { handle in
378
+ let data = handle.availableData
379
+ // When the child closes stderr, availableData returns empty and will keep re-firing; clear the handler
380
+ // to avoid a busy read loop on the file-descriptor monitoring queue.
381
+ if data.isEmpty {
382
+ handle.readabilityHandler = nil
383
+ return
384
+ }
385
+ guard let text = String(data: data, encoding: .utf8), !text.isEmpty else { return }
386
+ for line in text.split(whereSeparator: \.isNewline) {
387
+ Self.debugWriteStderr("[codex stderr] \(line)\n")
388
+ }
389
+ }
390
+ }
391
+
392
+ func initialize(clientName: String, clientVersion: String) async throws {
393
+ _ = try await self.request(
394
+ method: "initialize",
395
+ params: ["clientInfo": ["name": clientName, "version": clientVersion]])
396
+ try self.sendNotification(method: "initialized")
397
+ }
398
+
399
+ func fetchAccount() async throws -> RPCAccountResponse {
400
+ let message = try await self.request(method: "account/read")
401
+ return try self.decodeResult(from: message)
402
+ }
403
+
404
+ func fetchRateLimits() async throws -> RPCRateLimitsResponse {
405
+ let message = try await self.request(method: "account/rateLimits/read")
406
+ return try self.decodeResult(from: message)
407
+ }
408
+
409
+ func shutdown() {
410
+ self.stdoutPipe.fileHandleForReading.readabilityHandler = nil
411
+ self.stderrPipe.fileHandleForReading.readabilityHandler = nil
412
+ if self.process.isRunning {
413
+ self.process.terminate()
414
+ }
415
+ if let pgid = self.processGroup {
416
+ kill(-pgid, SIGTERM)
417
+ }
418
+ let waitDeadline = Date().addingTimeInterval(2.0)
419
+ while self.process.isRunning, Date() < waitDeadline {
420
+ usleep(100_000)
421
+ }
422
+ if self.process.isRunning {
423
+ if let pgid = self.processGroup {
424
+ kill(-pgid, SIGKILL)
425
+ }
426
+ kill(self.process.processIdentifier, SIGKILL)
427
+ }
428
+ try? self.stdinPipe.fileHandleForWriting.close()
429
+ try? self.stdoutPipe.fileHandleForReading.close()
430
+ try? self.stderrPipe.fileHandleForReading.close()
431
+ }
432
+
433
+ // MARK: - JSON-RPC helpers
434
+
435
+ private func request(method: String, params: [String: Any]? = nil) async throws -> [String: Any] {
436
+ let id = self.nextID
437
+ self.nextID += 1
438
+ try self.sendRequest(id: id, method: method, params: params)
439
+
440
+ while true {
441
+ let message = try await self.readNextMessage()
442
+
443
+ if message["id"] == nil, let methodName = message["method"] as? String {
444
+ Self.debugWriteStderr("[codex notify] \(methodName)\n")
445
+ continue
446
+ }
447
+
448
+ guard let messageID = self.jsonID(message["id"]), messageID == id else { continue }
449
+
450
+ if let error = message["error"] as? [String: Any], let messageText = error["message"] as? String {
451
+ throw RPCWireError.requestFailed(messageText)
452
+ }
453
+
454
+ return message
455
+ }
456
+ }
457
+
458
+ private func sendNotification(method: String, params: [String: Any]? = nil) throws {
459
+ let paramsValue: Any = params ?? [:]
460
+ try self.sendPayload(["method": method, "params": paramsValue])
461
+ }
462
+
463
+ private func sendRequest(id: Int, method: String, params: [String: Any]?) throws {
464
+ let paramsValue: Any = params ?? [:]
465
+ let payload: [String: Any] = ["id": id, "method": method, "params": paramsValue]
466
+ try self.sendPayload(payload)
467
+ }
468
+
469
+ private func sendPayload(_ payload: [String: Any]) throws {
470
+ let data = try JSONSerialization.data(withJSONObject: payload)
471
+ self.stdinPipe.fileHandleForWriting.write(data)
472
+ self.stdinPipe.fileHandleForWriting.write(Data([0x0A]))
473
+ }
474
+
475
+ private func readNextMessage() async throws -> [String: Any] {
476
+ for await lineData in self.stdoutLineStream {
477
+ if lineData.isEmpty { continue }
478
+ if let json = try? JSONSerialization.jsonObject(with: lineData) as? [String: Any] {
479
+ return json
480
+ }
481
+ }
482
+ throw RPCWireError.malformed("codex app-server closed stdout")
483
+ }
484
+
485
+ private func decodeResult<T: Decodable>(from message: [String: Any]) throws -> T {
486
+ guard let result = message["result"] else {
487
+ throw RPCWireError.malformed("missing result field")
488
+ }
489
+ let data = try JSONSerialization.data(withJSONObject: result)
490
+ let decoder = JSONDecoder()
491
+ return try decoder.decode(T.self, from: data)
492
+ }
493
+
494
+ private func jsonID(_ value: Any?) -> Int? {
495
+ switch value {
496
+ case let int as Int:
497
+ int
498
+ case let number as NSNumber:
499
+ number.intValue
500
+ default:
501
+ nil
502
+ }
503
+ }
504
+ }
505
+
506
+ // MARK: - Public fetcher used by the app
507
+
508
+ public struct UsageFetcher: Sendable {
509
+ private let environment: [String: String]
510
+
511
+ public init(environment: [String: String] = ProcessInfo.processInfo.environment) {
512
+ self.environment = environment
513
+ LoginShellPathCache.shared.captureOnce()
514
+ }
515
+
516
+ public func loadLatestUsage() async throws -> UsageSnapshot {
517
+ try await self.withFallback(primary: self.loadRPCUsage, secondary: self.loadTTYUsage)
518
+ }
519
+
520
+ private func loadRPCUsage() async throws -> UsageSnapshot {
521
+ let rpc = try CodexRPCClient()
522
+ defer { rpc.shutdown() }
523
+
524
+ try await rpc.initialize(clientName: "runic", clientVersion: "0.5.4")
525
+ // The app-server answers on a single stdout stream, so keep requests
526
+ // serialized to avoid starving one reader when multiple awaiters race
527
+ // for the same pipe.
528
+ let limits = try await rpc.fetchRateLimits().rateLimits
529
+ let account = try? await rpc.fetchAccount()
530
+
531
+ guard let primary = Self.makeWindow(from: limits.primary),
532
+ let secondary = Self.makeWindow(from: limits.secondary)
533
+ else {
534
+ throw UsageError.noRateLimitsFound
535
+ }
536
+
537
+ let identity = ProviderIdentitySnapshot(
538
+ providerID: .codex,
539
+ accountEmail: account?.account.flatMap { details in
540
+ if case let .chatgpt(email, _) = details { email } else { nil }
541
+ },
542
+ accountOrganization: nil,
543
+ loginMethod: account?.account.flatMap { details in
544
+ if case let .chatgpt(_, plan) = details { plan } else { nil }
545
+ })
546
+ return UsageSnapshot(
547
+ primary: primary,
548
+ secondary: secondary,
549
+ tertiary: nil,
550
+ updatedAt: Date(),
551
+ identity: identity)
552
+ }
553
+
554
+ private func loadTTYUsage() async throws -> UsageSnapshot {
555
+ let status = try await CodexStatusProbe().fetch()
556
+ guard let fiveLeft = status.fiveHourPercentLeft, let weekLeft = status.weeklyPercentLeft else {
557
+ throw UsageError.noRateLimitsFound
558
+ }
559
+
560
+ let primary = RateWindow(
561
+ usedPercent: max(0, 100 - Double(fiveLeft)),
562
+ windowMinutes: 300,
563
+ resetsAt: nil,
564
+ resetDescription: status.fiveHourResetDescription)
565
+ let secondary = RateWindow(
566
+ usedPercent: max(0, 100 - Double(weekLeft)),
567
+ windowMinutes: 10080,
568
+ resetsAt: nil,
569
+ resetDescription: status.weeklyResetDescription)
570
+
571
+ return UsageSnapshot(
572
+ primary: primary,
573
+ secondary: secondary,
574
+ tertiary: nil,
575
+ updatedAt: Date(),
576
+ identity: nil)
577
+ }
578
+
579
+ public func loadLatestCredits() async throws -> CreditsSnapshot {
580
+ try await self.withFallback(primary: self.loadRPCCredits, secondary: self.loadTTYCredits)
581
+ }
582
+
583
+ private func loadRPCCredits() async throws -> CreditsSnapshot {
584
+ let rpc = try CodexRPCClient()
585
+ defer { rpc.shutdown() }
586
+ try await rpc.initialize(clientName: "runic", clientVersion: "0.5.4")
587
+ let limits = try await rpc.fetchRateLimits().rateLimits
588
+ guard let credits = limits.credits else { throw UsageError.noRateLimitsFound }
589
+ let remaining = Self.parseCredits(credits.balance)
590
+ return CreditsSnapshot(remaining: remaining, events: [], updatedAt: Date())
591
+ }
592
+
593
+ private func loadTTYCredits() async throws -> CreditsSnapshot {
594
+ let status = try await CodexStatusProbe().fetch()
595
+ guard let credits = status.credits else { throw UsageError.noRateLimitsFound }
596
+ return CreditsSnapshot(remaining: credits, events: [], updatedAt: Date())
597
+ }
598
+
599
+ private func withFallback<T>(
600
+ primary: @escaping () async throws -> T,
601
+ secondary: @escaping () async throws -> T) async throws -> T
602
+ {
603
+ do {
604
+ return try await primary()
605
+ } catch let primaryError {
606
+ do {
607
+ return try await secondary()
608
+ } catch {
609
+ // Preserve the original failure so callers see the primary path error.
610
+ throw primaryError
611
+ }
612
+ }
613
+ }
614
+
615
+ public func debugRawRateLimits() async -> String {
616
+ do {
617
+ let rpc = try CodexRPCClient()
618
+ defer { rpc.shutdown() }
619
+ try await rpc.initialize(clientName: "runic", clientVersion: "0.5.4")
620
+ let limits = try await rpc.fetchRateLimits()
621
+ let data = try JSONEncoder().encode(limits)
622
+ return String(data: data, encoding: .utf8) ?? "<unprintable>"
623
+ } catch {
624
+ return "Codex RPC probe failed: \(error)"
625
+ }
626
+ }
627
+
628
+ public func loadAccountInfo() -> AccountInfo {
629
+ // Keep using auth.json for quick startup (non-blocking, no RPC spin-up required).
630
+ let authURL = URL(fileURLWithPath: self.environment["CODEX_HOME"] ?? "\(NSHomeDirectory())/.codex")
631
+ .appendingPathComponent("auth.json")
632
+ guard let data = try? Data(contentsOf: authURL),
633
+ let auth = try? JSONDecoder().decode(AuthFile.self, from: data),
634
+ let idToken = auth.tokens?.idToken
635
+ else {
636
+ return AccountInfo(email: nil, plan: nil)
637
+ }
638
+
639
+ guard let payload = UsageFetcher.parseJWT(idToken) else {
640
+ return AccountInfo(email: nil, plan: nil)
641
+ }
642
+
643
+ let authDict = payload["https://api.openai.com/auth"] as? [String: Any]
644
+ let profileDict = payload["https://api.openai.com/profile"] as? [String: Any]
645
+
646
+ let plan = (authDict?["chatgpt_plan_type"] as? String)
647
+ ?? (payload["chatgpt_plan_type"] as? String)
648
+
649
+ let email = (payload["email"] as? String)
650
+ ?? (profileDict?["email"] as? String)
651
+
652
+ return AccountInfo(email: email, plan: plan)
653
+ }
654
+
655
+ // MARK: - Helpers
656
+
657
+ private static func makeWindow(from rpc: RPCRateLimitWindow?) -> RateWindow? {
658
+ guard let rpc else { return nil }
659
+ let resetsAtDate = rpc.resetsAt.map { Date(timeIntervalSince1970: TimeInterval($0)) }
660
+ let resetDescription = resetsAtDate.map { UsageFormatter.resetDescription(from: $0) }
661
+ return RateWindow(
662
+ usedPercent: rpc.usedPercent,
663
+ windowMinutes: rpc.windowDurationMins,
664
+ resetsAt: resetsAtDate,
665
+ resetDescription: resetDescription)
666
+ }
667
+
668
+ private static func parseCredits(_ balance: String?) -> Double {
669
+ guard let balance, let val = Double(balance) else { return 0 }
670
+ return val
671
+ }
672
+
673
+ public static func parseJWT(_ token: String) -> [String: Any]? {
674
+ let parts = token.split(separator: ".")
675
+ guard parts.count >= 2 else { return nil }
676
+ let payloadPart = parts[1]
677
+
678
+ var padded = String(payloadPart)
679
+ .replacingOccurrences(of: "-", with: "+")
680
+ .replacingOccurrences(of: "_", with: "/")
681
+ while padded.count % 4 != 0 {
682
+ padded.append("=")
683
+ }
684
+ guard let data = Data(base64Encoded: padded) else { return nil }
685
+ guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
686
+ return json
687
+ }
688
+ }
689
+
690
+ // Minimal auth.json struct preserved from previous implementation
691
+ private struct AuthFile: Decodable {
692
+ struct Tokens: Decodable { let idToken: String? }
693
+ let tokens: Tokens?
694
+ }