@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,245 @@
1
+ import Foundation
2
+
3
+ public enum UsageLedgerProjectNameConfidence: String, Sendable, Codable {
4
+ case none
5
+ case low
6
+ case medium
7
+ case high
8
+
9
+ var rank: Int {
10
+ switch self {
11
+ case .none: 0
12
+ case .low: 1
13
+ case .medium: 2
14
+ case .high: 3
15
+ }
16
+ }
17
+ }
18
+
19
+ public enum UsageLedgerProjectNameSource: String, Sendable, Codable {
20
+ case unknown
21
+ case projectName
22
+ case projectID
23
+ case inferredFromPath
24
+ case inferredFromName
25
+ case budgetOverride
26
+ }
27
+
28
+ public struct UsageLedgerProjectIdentity: Sendable, Codable, Hashable {
29
+ public let key: String?
30
+ public let projectID: String?
31
+ public let displayName: String?
32
+ public let confidence: UsageLedgerProjectNameConfidence
33
+ public let source: UsageLedgerProjectNameSource
34
+ public let workspaceHash: String?
35
+ public let provenance: String?
36
+
37
+ public init(
38
+ key: String?,
39
+ projectID: String?,
40
+ displayName: String?,
41
+ confidence: UsageLedgerProjectNameConfidence,
42
+ source: UsageLedgerProjectNameSource,
43
+ workspaceHash: String?,
44
+ provenance: String?)
45
+ {
46
+ self.key = key
47
+ self.projectID = projectID
48
+ self.displayName = displayName
49
+ self.confidence = confidence
50
+ self.source = source
51
+ self.workspaceHash = workspaceHash
52
+ self.provenance = provenance
53
+ }
54
+ }
55
+
56
+ public enum UsageLedgerProjectIdentityResolver {
57
+ public static func resolve(
58
+ provider _: UsageProvider,
59
+ projectID: String?,
60
+ projectName: String?,
61
+ budgetNameOverride: String? = nil) -> UsageLedgerProjectIdentity
62
+ {
63
+ let normalizedProjectID = normalizedIdentifier(projectID)
64
+ let normalizedProjectName = normalizedDisplay(projectName)
65
+ let budgetName = normalizedDisplay(budgetNameOverride)
66
+ let workspaceCandidate = firstPathLikeValue(candidates: [projectID, projectName])
67
+ let workspaceHash = workspaceCandidate.map(stablePathHash)
68
+ let workspaceSuffix = workspaceHash.map { "#w:\($0.prefix(8))" } ?? ""
69
+
70
+ var displayName: String?
71
+ var confidence: UsageLedgerProjectNameConfidence = .none
72
+ var source: UsageLedgerProjectNameSource = .unknown
73
+ var provenance: String?
74
+
75
+ if let budgetName {
76
+ displayName = budgetName
77
+ confidence = .high
78
+ source = .budgetOverride
79
+ provenance = "budget_override"
80
+ } else if let normalizedProjectName {
81
+ displayName = normalizedProjectName
82
+ confidence = .high
83
+ source = .projectName
84
+ provenance = "entry.projectName"
85
+ } else if let pathTail = inferredPathTail(from: projectID) {
86
+ displayName = pathTail
87
+ confidence = .medium
88
+ source = .inferredFromPath
89
+ provenance = "path_tail(projectID)"
90
+ } else if let readableID = readableIdentifier(normalizedProjectID) {
91
+ displayName = readableID
92
+ confidence = .low
93
+ source = .projectID
94
+ provenance = "entry.projectID"
95
+ } else if let inferredFromName = inferredNameToken(from: projectName) {
96
+ displayName = inferredFromName
97
+ confidence = .low
98
+ source = .inferredFromName
99
+ provenance = "inferred(projectName)"
100
+ }
101
+
102
+ let key: String?
103
+ if let normalizedProjectID {
104
+ key = "id:\(normalizedProjectID.lowercased())\(workspaceSuffix)"
105
+ } else if let seed = displayName {
106
+ let slug = slugify(seed)
107
+ key = slug.isEmpty ? nil : "name:\(slug)\(workspaceSuffix)"
108
+ } else {
109
+ key = nil
110
+ }
111
+
112
+ return UsageLedgerProjectIdentity(
113
+ key: key,
114
+ projectID: normalizedProjectID,
115
+ displayName: displayName,
116
+ confidence: confidence,
117
+ source: source,
118
+ workspaceHash: workspaceHash,
119
+ provenance: provenance)
120
+ }
121
+
122
+ public static func fallbackDisplayName(projectID: String?) -> String? {
123
+ if let tail = inferredPathTail(from: projectID) {
124
+ return tail
125
+ }
126
+ guard let normalized = normalizedIdentifier(projectID), !normalized.isEmpty else {
127
+ return nil
128
+ }
129
+ if normalized.contains("/") || normalized.contains("\\") {
130
+ return nil
131
+ }
132
+ return readableIdentifier(normalized)
133
+ }
134
+
135
+ private static func normalizedIdentifier(_ value: String?) -> String? {
136
+ let cleaned = normalizedDisplay(value)
137
+ guard let cleaned, !cleaned.isEmpty else { return nil }
138
+ return cleaned
139
+ }
140
+
141
+ private static func normalizedDisplay(_ value: String?) -> String? {
142
+ guard let value else { return nil }
143
+ let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
144
+ guard !trimmed.isEmpty else { return nil }
145
+ let pieces = trimmed.split(whereSeparator: { $0.isWhitespace })
146
+ guard !pieces.isEmpty else { return nil }
147
+ return pieces.joined(separator: " ")
148
+ }
149
+
150
+ private static func inferredPathTail(from value: String?) -> String? {
151
+ guard let value else { return nil }
152
+ let separators = CharacterSet(charactersIn: "/\\")
153
+ guard value.rangeOfCharacter(from: separators) != nil else { return nil }
154
+
155
+ let parts = value.split { ch in
156
+ ch == "/" || ch == "\\"
157
+ }
158
+ guard let last = parts.last else { return nil }
159
+ let tail = normalizedDisplay(String(last))
160
+ guard let tail else { return nil }
161
+ guard !looksOpaqueIdentifier(tail) else { return nil }
162
+ return tail
163
+ }
164
+
165
+ private static func readableIdentifier(_ value: String?) -> String? {
166
+ guard let value else { return nil }
167
+ guard !value.isEmpty else { return nil }
168
+ guard !looksOpaqueIdentifier(value) else { return nil }
169
+ return value
170
+ }
171
+
172
+ private static func inferredNameToken(from value: String?) -> String? {
173
+ let normalized = normalizedDisplay(value)
174
+ guard let normalized, !normalized.isEmpty else { return nil }
175
+ let slug = slugify(normalized)
176
+ guard !slug.isEmpty else { return nil }
177
+ let titleCased = slug.split(separator: "-")
178
+ .map { token -> String in
179
+ guard let first = token.first else { return "" }
180
+ return "\(String(first).uppercased())\(token.dropFirst())"
181
+ }
182
+ .joined(separator: " ")
183
+ return titleCased.isEmpty ? nil : titleCased
184
+ }
185
+
186
+ private static func looksOpaqueIdentifier(_ value: String) -> Bool {
187
+ if value.count >= 24 {
188
+ let opaqueCharset = CharacterSet(charactersIn: "0123456789abcdefABCDEF-")
189
+ if value.unicodeScalars.allSatisfy({ opaqueCharset.contains($0) }) {
190
+ return true
191
+ }
192
+ }
193
+ if value.count > 64 {
194
+ return true
195
+ }
196
+ return false
197
+ }
198
+
199
+ private static func slugify(_ value: String) -> String {
200
+ let folded = value.folding(options: [.diacriticInsensitive, .caseInsensitive, .widthInsensitive], locale: .init(identifier: "en_US_POSIX"))
201
+ var scalarBuffer: [UnicodeScalar] = []
202
+ scalarBuffer.reserveCapacity(folded.unicodeScalars.count)
203
+
204
+ let allowed = CharacterSet.alphanumerics
205
+ var previousWasDash = false
206
+ for scalar in folded.unicodeScalars {
207
+ if allowed.contains(scalar) {
208
+ scalarBuffer.append(UnicodeScalar(String(scalar).lowercased()) ?? scalar)
209
+ previousWasDash = false
210
+ } else if !previousWasDash {
211
+ scalarBuffer.append("-")
212
+ previousWasDash = true
213
+ }
214
+ }
215
+
216
+ var slug = String(String.UnicodeScalarView(scalarBuffer))
217
+ while slug.hasPrefix("-") {
218
+ slug.removeFirst()
219
+ }
220
+ while slug.hasSuffix("-") {
221
+ slug.removeLast()
222
+ }
223
+ return slug
224
+ }
225
+
226
+ private static func firstPathLikeValue(candidates: [String?]) -> String? {
227
+ for candidate in candidates {
228
+ guard let candidate else { continue }
229
+ if candidate.contains("/") || candidate.contains("\\") {
230
+ return candidate
231
+ }
232
+ }
233
+ return nil
234
+ }
235
+
236
+ private static func stablePathHash(_ value: String) -> String {
237
+ let normalized = value.lowercased()
238
+ var hash: UInt64 = 0xcbf29ce484222325
239
+ for byte in normalized.utf8 {
240
+ hash ^= UInt64(byte)
241
+ hash = hash &* 0x100000001b3
242
+ }
243
+ return String(format: "%016llx", hash)
244
+ }
245
+ }
@@ -0,0 +1,81 @@
1
+ import Foundation
2
+
3
+ public struct UsagePace: Sendable {
4
+ public enum Stage: Sendable {
5
+ case onTrack
6
+ case slightlyAhead
7
+ case ahead
8
+ case farAhead
9
+ case slightlyBehind
10
+ case behind
11
+ case farBehind
12
+ }
13
+
14
+ public let stage: Stage
15
+ public let deltaPercent: Double
16
+ public let expectedUsedPercent: Double
17
+ public let actualUsedPercent: Double
18
+ public let etaSeconds: TimeInterval?
19
+ public let willLastToReset: Bool
20
+
21
+ public static func weekly(
22
+ window: RateWindow,
23
+ now: Date = .init(),
24
+ defaultWindowMinutes: Int = 10080) -> UsagePace?
25
+ {
26
+ guard let resetsAt = window.resetsAt else { return nil }
27
+ let minutes = window.windowMinutes ?? defaultWindowMinutes
28
+ guard minutes > 0 else { return nil }
29
+
30
+ let duration = TimeInterval(minutes) * 60
31
+ let timeUntilReset = resetsAt.timeIntervalSince(now)
32
+ guard timeUntilReset > 0 else { return nil }
33
+ guard timeUntilReset <= duration else { return nil }
34
+ let elapsed = Self.clamp(duration - timeUntilReset, lower: 0, upper: duration)
35
+ let expected = Self.clamp((elapsed / duration) * 100, lower: 0, upper: 100)
36
+ let actual = Self.clamp(window.usedPercent, lower: 0, upper: 100)
37
+ if elapsed == 0, actual > 0 {
38
+ return nil
39
+ }
40
+ let delta = actual - expected
41
+ let stage = Self.stage(for: delta)
42
+
43
+ var etaSeconds: TimeInterval?
44
+ var willLastToReset = false
45
+
46
+ if elapsed > 0, actual > 0 {
47
+ let rate = actual / elapsed
48
+ if rate > 0 {
49
+ let remaining = max(0, 100 - actual)
50
+ let candidate = remaining / rate
51
+ if candidate >= timeUntilReset {
52
+ willLastToReset = true
53
+ } else {
54
+ etaSeconds = candidate
55
+ }
56
+ }
57
+ } else if elapsed > 0, actual == 0 {
58
+ willLastToReset = true
59
+ }
60
+
61
+ return UsagePace(
62
+ stage: stage,
63
+ deltaPercent: delta,
64
+ expectedUsedPercent: expected,
65
+ actualUsedPercent: actual,
66
+ etaSeconds: etaSeconds,
67
+ willLastToReset: willLastToReset)
68
+ }
69
+
70
+ private static func stage(for delta: Double) -> Stage {
71
+ let absDelta = abs(delta)
72
+ if absDelta <= 2 { return .onTrack }
73
+ if absDelta <= 6 { return delta >= 0 ? .slightlyAhead : .slightlyBehind }
74
+ if absDelta <= 12 { return delta >= 0 ? .ahead : .behind }
75
+ return delta >= 0 ? .farAhead : .farBehind
76
+ }
77
+
78
+ private static func clamp(_ value: Double, lower: Double, upper: Double) -> Double {
79
+ min(upper, max(lower, value))
80
+ }
81
+ }
@@ -0,0 +1,73 @@
1
+ import Foundation
2
+
3
+ enum CostUsageCacheIO {
4
+ private static func defaultCacheRoot() -> URL {
5
+ let root = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
6
+ return root.appendingPathComponent("Runic", isDirectory: true)
7
+ }
8
+
9
+ static func cacheFileURL(provider: UsageProvider, cacheRoot: URL? = nil) -> URL {
10
+ let root = cacheRoot ?? self.defaultCacheRoot()
11
+ return root
12
+ .appendingPathComponent("cost-usage", isDirectory: true)
13
+ .appendingPathComponent("\(provider.rawValue)-v1.json", isDirectory: false)
14
+ }
15
+
16
+ static func load(provider: UsageProvider, cacheRoot: URL? = nil) -> CostUsageCache {
17
+ let url = self.cacheFileURL(provider: provider, cacheRoot: cacheRoot)
18
+ if let decoded = self.loadCache(at: url) { return decoded }
19
+ return CostUsageCache()
20
+ }
21
+
22
+ private static func loadCache(at url: URL) -> CostUsageCache? {
23
+ guard let data = try? Data(contentsOf: url) else { return nil }
24
+ guard let decoded = try? JSONDecoder().decode(CostUsageCache.self, from: data)
25
+ else { return nil }
26
+ guard decoded.version == 1 else { return nil }
27
+ return decoded
28
+ }
29
+
30
+ static func save(provider: UsageProvider, cache: CostUsageCache, cacheRoot: URL? = nil) {
31
+ let url = self.cacheFileURL(provider: provider, cacheRoot: cacheRoot)
32
+ let dir = url.deletingLastPathComponent()
33
+ try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
34
+
35
+ let tmp = dir.appendingPathComponent(".tmp-\(UUID().uuidString).json", isDirectory: false)
36
+ let data = (try? JSONEncoder().encode(cache)) ?? Data()
37
+ do {
38
+ try data.write(to: tmp, options: [.atomic])
39
+ _ = try FileManager.default.replaceItemAt(url, withItemAt: tmp)
40
+ } catch {
41
+ try? FileManager.default.removeItem(at: tmp)
42
+ }
43
+ }
44
+ }
45
+
46
+ struct CostUsageCache: Codable, Sendable {
47
+ var version: Int = 1
48
+ var lastScanUnixMs: Int64 = 0
49
+
50
+ // filePath -> file usage
51
+ var files: [String: CostUsageFileUsage] = [:]
52
+
53
+ // dayKey -> model -> packed usage
54
+ var days: [String: [String: [Int]]] = [:]
55
+
56
+ // rootPath -> mtime (for Claude roots)
57
+ var roots: [String: Int64]?
58
+ }
59
+
60
+ struct CostUsageFileUsage: Codable, Sendable {
61
+ var mtimeUnixMs: Int64
62
+ var size: Int64
63
+ var days: [String: [String: [Int]]]
64
+ var parsedBytes: Int64?
65
+ var lastModel: String?
66
+ var lastTotals: CostUsageCodexTotals?
67
+ }
68
+
69
+ struct CostUsageCodexTotals: Codable, Sendable {
70
+ var input: Int
71
+ var cached: Int
72
+ var output: Int
73
+ }
@@ -0,0 +1,74 @@
1
+ import Foundation
2
+
3
+ enum CostUsageJsonl {
4
+ struct Line: Sendable {
5
+ let bytes: Data
6
+ let wasTruncated: Bool
7
+ }
8
+
9
+ @discardableResult
10
+ static func scan(
11
+ fileURL: URL,
12
+ offset: Int64 = 0,
13
+ maxLineBytes: Int,
14
+ prefixBytes: Int,
15
+ onLine: (Line) -> Void) throws
16
+ -> Int64
17
+ {
18
+ let handle = try FileHandle(forReadingFrom: fileURL)
19
+ defer { try? handle.close() }
20
+
21
+ let startOffset = max(0, offset)
22
+ if startOffset > 0 {
23
+ try handle.seek(toOffset: UInt64(startOffset))
24
+ }
25
+
26
+ var buffer = Data()
27
+ buffer.reserveCapacity(64 * 1024)
28
+
29
+ var current = Data()
30
+ current.reserveCapacity(4 * 1024)
31
+ var lineBytes = 0
32
+ var truncated = false
33
+ var bytesRead: Int64 = 0
34
+
35
+ func flushLine() {
36
+ guard lineBytes > 0 else { return }
37
+ let line = Line(bytes: current, wasTruncated: truncated)
38
+ onLine(line)
39
+ current.removeAll(keepingCapacity: true)
40
+ lineBytes = 0
41
+ truncated = false
42
+ }
43
+
44
+ while true {
45
+ let chunk = try handle.read(upToCount: 256 * 1024) ?? Data()
46
+ if chunk.isEmpty {
47
+ flushLine()
48
+ break
49
+ }
50
+
51
+ bytesRead += Int64(chunk.count)
52
+ buffer.append(chunk)
53
+
54
+ while true {
55
+ guard let nl = buffer.firstIndex(of: 0x0A) else { break }
56
+ let linePart = buffer[..<nl]
57
+ buffer.removeSubrange(...nl)
58
+
59
+ lineBytes += linePart.count
60
+ if !truncated {
61
+ if lineBytes > maxLineBytes || lineBytes > prefixBytes {
62
+ truncated = true
63
+ current.removeAll(keepingCapacity: true)
64
+ } else {
65
+ current.append(contentsOf: linePart)
66
+ }
67
+ }
68
+ flushLine()
69
+ }
70
+ }
71
+
72
+ return startOffset + bytesRead
73
+ }
74
+ }
@@ -0,0 +1,208 @@
1
+ import Foundation
2
+
3
+ enum CostUsagePricing {
4
+ struct CodexPricing: Sendable {
5
+ let inputCostPerToken: Double
6
+ let outputCostPerToken: Double
7
+ let cacheReadInputCostPerToken: Double
8
+ }
9
+
10
+ struct ClaudePricing: Sendable {
11
+ let inputCostPerToken: Double
12
+ let outputCostPerToken: Double
13
+ let cacheCreationInputCostPerToken: Double
14
+ let cacheReadInputCostPerToken: Double
15
+
16
+ let thresholdTokens: Int?
17
+ let inputCostPerTokenAboveThreshold: Double?
18
+ let outputCostPerTokenAboveThreshold: Double?
19
+ let cacheCreationInputCostPerTokenAboveThreshold: Double?
20
+ let cacheReadInputCostPerTokenAboveThreshold: Double?
21
+ }
22
+
23
+ private static let codex: [String: CodexPricing] = [
24
+ "gpt-5": CodexPricing(
25
+ inputCostPerToken: 1.25e-6,
26
+ outputCostPerToken: 1e-5,
27
+ cacheReadInputCostPerToken: 1.25e-7),
28
+ "gpt-5-codex": CodexPricing(
29
+ inputCostPerToken: 1.25e-6,
30
+ outputCostPerToken: 1e-5,
31
+ cacheReadInputCostPerToken: 1.25e-7),
32
+ "gpt-5.1": CodexPricing(
33
+ inputCostPerToken: 1.25e-6,
34
+ outputCostPerToken: 1e-5,
35
+ cacheReadInputCostPerToken: 1.25e-7),
36
+ "gpt-5.2": CodexPricing(
37
+ inputCostPerToken: 1.75e-6,
38
+ outputCostPerToken: 1.4e-5,
39
+ cacheReadInputCostPerToken: 1.75e-7),
40
+ "gpt-5.2-codex": CodexPricing(
41
+ inputCostPerToken: 1.75e-6,
42
+ outputCostPerToken: 1.4e-5,
43
+ cacheReadInputCostPerToken: 1.75e-7),
44
+ ]
45
+
46
+ private static let claude: [String: ClaudePricing] = [
47
+ "claude-haiku-4-5-20251001": ClaudePricing(
48
+ inputCostPerToken: 1e-6,
49
+ outputCostPerToken: 5e-6,
50
+ cacheCreationInputCostPerToken: 1.25e-6,
51
+ cacheReadInputCostPerToken: 1e-7,
52
+ thresholdTokens: nil,
53
+ inputCostPerTokenAboveThreshold: nil,
54
+ outputCostPerTokenAboveThreshold: nil,
55
+ cacheCreationInputCostPerTokenAboveThreshold: nil,
56
+ cacheReadInputCostPerTokenAboveThreshold: nil),
57
+ "claude-opus-4-5-20251101": ClaudePricing(
58
+ inputCostPerToken: 5e-6,
59
+ outputCostPerToken: 2.5e-5,
60
+ cacheCreationInputCostPerToken: 6.25e-6,
61
+ cacheReadInputCostPerToken: 5e-7,
62
+ thresholdTokens: nil,
63
+ inputCostPerTokenAboveThreshold: nil,
64
+ outputCostPerTokenAboveThreshold: nil,
65
+ cacheCreationInputCostPerTokenAboveThreshold: nil,
66
+ cacheReadInputCostPerTokenAboveThreshold: nil),
67
+ "claude-sonnet-4-5": ClaudePricing(
68
+ inputCostPerToken: 3e-6,
69
+ outputCostPerToken: 1.5e-5,
70
+ cacheCreationInputCostPerToken: 3.75e-6,
71
+ cacheReadInputCostPerToken: 3e-7,
72
+ thresholdTokens: 200_000,
73
+ inputCostPerTokenAboveThreshold: 6e-6,
74
+ outputCostPerTokenAboveThreshold: 2.25e-5,
75
+ cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,
76
+ cacheReadInputCostPerTokenAboveThreshold: 6e-7),
77
+ "claude-sonnet-4-5-20250929": ClaudePricing(
78
+ inputCostPerToken: 3e-6,
79
+ outputCostPerToken: 1.5e-5,
80
+ cacheCreationInputCostPerToken: 3.75e-6,
81
+ cacheReadInputCostPerToken: 3e-7,
82
+ thresholdTokens: 200_000,
83
+ inputCostPerTokenAboveThreshold: 6e-6,
84
+ outputCostPerTokenAboveThreshold: 2.25e-5,
85
+ cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,
86
+ cacheReadInputCostPerTokenAboveThreshold: 6e-7),
87
+ "claude-opus-4-20250514": ClaudePricing(
88
+ inputCostPerToken: 1.5e-5,
89
+ outputCostPerToken: 7.5e-5,
90
+ cacheCreationInputCostPerToken: 1.875e-5,
91
+ cacheReadInputCostPerToken: 1.5e-6,
92
+ thresholdTokens: nil,
93
+ inputCostPerTokenAboveThreshold: nil,
94
+ outputCostPerTokenAboveThreshold: nil,
95
+ cacheCreationInputCostPerTokenAboveThreshold: nil,
96
+ cacheReadInputCostPerTokenAboveThreshold: nil),
97
+ "claude-opus-4-1": ClaudePricing(
98
+ inputCostPerToken: 1.5e-5,
99
+ outputCostPerToken: 7.5e-5,
100
+ cacheCreationInputCostPerToken: 1.875e-5,
101
+ cacheReadInputCostPerToken: 1.5e-6,
102
+ thresholdTokens: nil,
103
+ inputCostPerTokenAboveThreshold: nil,
104
+ outputCostPerTokenAboveThreshold: nil,
105
+ cacheCreationInputCostPerTokenAboveThreshold: nil,
106
+ cacheReadInputCostPerTokenAboveThreshold: nil),
107
+ "claude-sonnet-4-20250514": ClaudePricing(
108
+ inputCostPerToken: 3e-6,
109
+ outputCostPerToken: 1.5e-5,
110
+ cacheCreationInputCostPerToken: 3.75e-6,
111
+ cacheReadInputCostPerToken: 3e-7,
112
+ thresholdTokens: 200_000,
113
+ inputCostPerTokenAboveThreshold: 6e-6,
114
+ outputCostPerTokenAboveThreshold: 2.25e-5,
115
+ cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,
116
+ cacheReadInputCostPerTokenAboveThreshold: 6e-7),
117
+ ]
118
+
119
+ static func normalizeCodexModel(_ raw: String) -> String {
120
+ var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
121
+ if trimmed.hasPrefix("openai/") {
122
+ trimmed = String(trimmed.dropFirst("openai/".count))
123
+ }
124
+ if let codexRange = trimmed.range(of: "-codex") {
125
+ let base = String(trimmed[..<codexRange.lowerBound])
126
+ if self.codex[base] != nil { return base }
127
+ }
128
+ return trimmed
129
+ }
130
+
131
+ static func normalizeClaudeModel(_ raw: String) -> String {
132
+ var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
133
+ if trimmed.hasPrefix("anthropic.") {
134
+ trimmed = String(trimmed.dropFirst("anthropic.".count))
135
+ }
136
+
137
+ if let lastDot = trimmed.lastIndex(of: "."),
138
+ trimmed.contains("claude-")
139
+ {
140
+ let tail = String(trimmed[trimmed.index(after: lastDot)...])
141
+ if tail.hasPrefix("claude-") {
142
+ trimmed = tail
143
+ }
144
+ }
145
+
146
+ if let vRange = trimmed.range(of: #"-v\d+:\d+$"#, options: .regularExpression) {
147
+ trimmed.removeSubrange(vRange)
148
+ }
149
+
150
+ if let baseRange = trimmed.range(of: #"-\d{8}$"#, options: .regularExpression) {
151
+ let base = String(trimmed[..<baseRange.lowerBound])
152
+ if self.claude[base] != nil {
153
+ return base
154
+ }
155
+ }
156
+
157
+ return trimmed
158
+ }
159
+
160
+ static func codexCostUSD(model: String, inputTokens: Int, cachedInputTokens: Int, outputTokens: Int) -> Double? {
161
+ let key = self.normalizeCodexModel(model)
162
+ guard let pricing = self.codex[key] else { return nil }
163
+ let cached = min(max(0, cachedInputTokens), max(0, inputTokens))
164
+ let nonCached = max(0, inputTokens - cached)
165
+ return Double(nonCached) * pricing.inputCostPerToken
166
+ + Double(cached) * pricing.cacheReadInputCostPerToken
167
+ + Double(max(0, outputTokens)) * pricing.outputCostPerToken
168
+ }
169
+
170
+ static func claudeCostUSD(
171
+ model: String,
172
+ inputTokens: Int,
173
+ cacheReadInputTokens: Int,
174
+ cacheCreationInputTokens: Int,
175
+ outputTokens: Int) -> Double?
176
+ {
177
+ let key = self.normalizeClaudeModel(model)
178
+ guard let pricing = self.claude[key] else { return nil }
179
+
180
+ func tiered(_ tokens: Int, base: Double, above: Double?, threshold: Int?) -> Double {
181
+ guard let threshold, let above else { return Double(tokens) * base }
182
+ let below = min(tokens, threshold)
183
+ let over = max(tokens - threshold, 0)
184
+ return Double(below) * base + Double(over) * above
185
+ }
186
+
187
+ return tiered(
188
+ max(0, inputTokens),
189
+ base: pricing.inputCostPerToken,
190
+ above: pricing.inputCostPerTokenAboveThreshold,
191
+ threshold: pricing.thresholdTokens)
192
+ + tiered(
193
+ max(0, cacheReadInputTokens),
194
+ base: pricing.cacheReadInputCostPerToken,
195
+ above: pricing.cacheReadInputCostPerTokenAboveThreshold,
196
+ threshold: pricing.thresholdTokens)
197
+ + tiered(
198
+ max(0, cacheCreationInputTokens),
199
+ base: pricing.cacheCreationInputCostPerToken,
200
+ above: pricing.cacheCreationInputCostPerTokenAboveThreshold,
201
+ threshold: pricing.thresholdTokens)
202
+ + tiered(
203
+ max(0, outputTokens),
204
+ base: pricing.outputCostPerToken,
205
+ above: pricing.outputCostPerTokenAboveThreshold,
206
+ threshold: pricing.thresholdTokens)
207
+ }
208
+ }