@sriinnu/runic 2.0.0 → 2.11.1

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 (610) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/LICENSE +373 -21
  3. package/README.md +96 -89
  4. package/THIRD-PARTY-NOTICES.md +651 -0
  5. package/package.json +14 -4
  6. package/.swiftformat +0 -50
  7. package/.swiftlint.yml +0 -160
  8. package/CONTRIBUTING.md +0 -456
  9. package/Icon.icns +0 -0
  10. package/Icon.icon/Assets/runic.png +0 -0
  11. package/Icon.icon/icon.json +0 -36
  12. package/Package.resolved +0 -51
  13. package/Package.swift +0 -136
  14. package/Packages/Helix/LICENSE +0 -21
  15. package/Packages/Helix/Package.swift +0 -29
  16. package/Packages/Helix/README.md +0 -976
  17. package/Packages/Helix/Sources/Helix/Binding.swift +0 -111
  18. package/Packages/Helix/Sources/Helix/CommandParser.swift +0 -160
  19. package/Packages/Helix/Sources/Helix/CommandSignature.swift +0 -117
  20. package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +0 -72
  21. package/Packages/Helix/Sources/Helix/HelpGenerator.swift +0 -175
  22. package/Packages/Helix/Sources/Helix/NameSpecification.swift +0 -98
  23. package/Packages/Helix/Sources/Helix/ParsableCommand.swift +0 -204
  24. package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +0 -44
  25. package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +0 -34
  26. package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +0 -91
  27. package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +0 -94
  28. package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +0 -167
  29. package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +0 -99
  30. package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +0 -146
  31. package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +0 -97
  32. package/Packages/Helix/Sources/Helix/Program.swift +0 -204
  33. package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +0 -367
  34. package/Packages/Silo/LICENSE +0 -21
  35. package/Packages/Silo/Package.swift +0 -34
  36. package/Packages/Silo/README.md +0 -466
  37. package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +0 -169
  38. package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +0 -365
  39. package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +0 -594
  40. package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +0 -326
  41. package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +0 -154
  42. package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +0 -119
  43. package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +0 -121
  44. package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +0 -391
  45. package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +0 -240
  46. package/Packages/Silo/Sources/Silo/CryptoSupport.swift +0 -86
  47. package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +0 -172
  48. package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +0 -172
  49. package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +0 -105
  50. package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +0 -191
  51. package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +0 -41
  52. package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +0 -110
  53. package/RuniciOS/App/RuniciOSApp.swift +0 -105
  54. package/RuniciOS/Core/RunicTypographyiOS.swift +0 -24
  55. package/RuniciOS/Core/iOSUsageStore.swift +0 -192
  56. package/RuniciOS/Views/ProviderDetailView.swift +0 -312
  57. package/RuniciOS/Views/ProviderListView.swift +0 -228
  58. package/RuniciOS/Widgets/LargeWidget.swift +0 -367
  59. package/RuniciOS/Widgets/LiveActivity.swift +0 -342
  60. package/RuniciOS/Widgets/LockScreenWidget.swift +0 -273
  61. package/RuniciOS/Widgets/MediumWidget.swift +0 -370
  62. package/RuniciOS/Widgets/RunicWidget.swift +0 -274
  63. package/RuniciOS/Widgets/SmallWidget.swift +0 -281
  64. package/RuniciOS/Widgets/WidgetModels.swift +0 -285
  65. package/RuniciOS/Widgets/WidgetTimeline.swift +0 -270
  66. package/Scripts/CREATION-SUMMARY.md +0 -367
  67. package/Scripts/DIRECTORY-STRUCTURE.md +0 -358
  68. package/Scripts/GETTING-STARTED-CHECKLIST.md +0 -364
  69. package/Scripts/INDEX.md +0 -365
  70. package/Scripts/QUICK-REFERENCE.md +0 -164
  71. package/Scripts/README-SCRIPTS.md +0 -926
  72. package/Scripts/build-android.sh +0 -526
  73. package/Scripts/build-ios.sh +0 -529
  74. package/Scripts/build-macos.sh +0 -603
  75. package/Scripts/build-windows.sh +0 -504
  76. package/Scripts/build_icon.sh +0 -49
  77. package/Scripts/changelog-to-html.sh +0 -89
  78. package/Scripts/check-release-assets.sh +0 -10
  79. package/Scripts/ci-config.yml +0 -474
  80. package/Scripts/compile_and_run.sh +0 -219
  81. package/Scripts/compile_and_run_adhoc.sh +0 -4
  82. package/Scripts/docs-list.mjs +0 -119
  83. package/Scripts/make_appcast.sh +0 -58
  84. package/Scripts/package_app.sh +0 -409
  85. package/Scripts/release-lib.sh +0 -214
  86. package/Scripts/release.sh +0 -76
  87. package/Scripts/setup-all.sh +0 -387
  88. package/Scripts/setup-ios.sh +0 -404
  89. package/Scripts/setup-react-native.sh +0 -523
  90. package/Scripts/sign-and-notarize.sh +0 -122
  91. package/Scripts/test_live_update.sh +0 -38
  92. package/Scripts/validate_changelog.sh +0 -23
  93. package/Scripts/verify_appcast.sh +0 -92
  94. package/Sources/Runic/About.swift +0 -93
  95. package/Sources/Runic/AppNotifications.swift +0 -117
  96. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +0 -670
  97. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +0 -519
  98. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +0 -824
  99. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +0 -90
  100. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +0 -1412
  101. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +0 -339
  102. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +0 -423
  103. package/Sources/Runic/Controllers/StatusItemController.swift +0 -360
  104. package/Sources/Runic/Core/BudgetNotificationManager.swift +0 -81
  105. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +0 -103
  106. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +0 -154
  107. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +0 -206
  108. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +0 -92
  109. package/Sources/Runic/Core/Rendering/IconRenderer.swift +0 -1222
  110. package/Sources/Runic/Core/Rendering/IconView.swift +0 -122
  111. package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +0 -71
  112. package/Sources/Runic/Core/RunicAnimation.swift +0 -53
  113. package/Sources/Runic/Core/RunicColors.swift +0 -73
  114. package/Sources/Runic/Core/RunicCornerRadius.swift +0 -15
  115. package/Sources/Runic/Core/RunicSpacing.swift +0 -68
  116. package/Sources/Runic/Core/RunicTypography.swift +0 -206
  117. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +0 -117
  118. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +0 -117
  119. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +0 -117
  120. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +0 -117
  121. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +0 -117
  122. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +0 -158
  123. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +0 -117
  124. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +0 -117
  125. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +0 -117
  126. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +0 -117
  127. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +0 -117
  128. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +0 -117
  129. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +0 -117
  130. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +0 -117
  131. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +0 -117
  132. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +0 -117
  133. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +0 -30
  134. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +0 -117
  135. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +0 -117
  136. package/Sources/Runic/Core/Stores/SettingsStore.swift +0 -1749
  137. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +0 -117
  138. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +0 -33
  139. package/Sources/Runic/Core/Stores/UsageStore+Status.swift +0 -189
  140. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +0 -33
  141. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +0 -67
  142. package/Sources/Runic/Core/Stores/UsageStore.swift +0 -3631
  143. package/Sources/Runic/Core/Stores/XAITokenStore.swift +0 -117
  144. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +0 -117
  145. package/Sources/Runic/Core/UsageExporter.swift +0 -197
  146. package/Sources/Runic/Date+RelativeDescription.swift +0 -21
  147. package/Sources/Runic/HiddenWindowView.swift +0 -32
  148. package/Sources/Runic/InstallOrigin.swift +0 -9
  149. package/Sources/Runic/KeyboardShortcuts+Names.swift +0 -6
  150. package/Sources/Runic/MouseLocationReader.swift +0 -65
  151. package/Sources/Runic/Notifications+Runic.swift +0 -6
  152. package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +0 -503
  153. package/Sources/Runic/ProviderRegistry.swift +0 -100
  154. package/Sources/Runic/ProviderSwitcherButtons.swift +0 -267
  155. package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +0 -11
  156. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +0 -14
  157. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +0 -24
  158. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +0 -52
  159. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +0 -42
  160. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +0 -24
  161. package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +0 -26
  162. package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +0 -14
  163. package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +0 -18
  164. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +0 -15
  165. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +0 -24
  166. package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +0 -193
  167. package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +0 -110
  168. package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +0 -28
  169. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +0 -15
  170. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +0 -24
  171. package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +0 -12
  172. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +0 -15
  173. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +0 -24
  174. package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +0 -20
  175. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +0 -15
  176. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +0 -24
  177. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +0 -24
  178. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +0 -32
  179. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +0 -24
  180. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +0 -24
  181. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +0 -24
  182. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +0 -24
  183. package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +0 -15
  184. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +0 -51
  185. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +0 -66
  186. package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +0 -12
  187. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +0 -99
  188. package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +0 -18
  189. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +0 -24
  190. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +0 -24
  191. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +0 -24
  192. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  193. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  194. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  195. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  196. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  197. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  198. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  199. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  200. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  201. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  202. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  203. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  204. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  205. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  206. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  207. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  208. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  209. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  210. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  211. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  212. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  213. package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +0 -2
  214. package/Sources/Runic/Resources/ProviderIcon-auggie.svg +0 -3
  215. package/Sources/Runic/Resources/ProviderIcon-azure.svg +0 -5
  216. package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +0 -7
  217. package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +0 -3
  218. package/Sources/Runic/Resources/ProviderIcon-claude.svg +0 -1
  219. package/Sources/Runic/Resources/ProviderIcon-codex.svg +0 -1
  220. package/Sources/Runic/Resources/ProviderIcon-cohere.svg +0 -3
  221. package/Sources/Runic/Resources/ProviderIcon-copilot.svg +0 -1
  222. package/Sources/Runic/Resources/ProviderIcon-cursor.svg +0 -3
  223. package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +0 -4
  224. package/Sources/Runic/Resources/ProviderIcon-factory.svg +0 -3
  225. package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +0 -3
  226. package/Sources/Runic/Resources/ProviderIcon-gemini.svg +0 -1
  227. package/Sources/Runic/Resources/ProviderIcon-groq.svg +0 -1
  228. package/Sources/Runic/Resources/ProviderIcon-kimi.svg +0 -4
  229. package/Sources/Runic/Resources/ProviderIcon-minimax.svg +0 -1
  230. package/Sources/Runic/Resources/ProviderIcon-mistral.svg +0 -5
  231. package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +0 -7
  232. package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +0 -4
  233. package/Sources/Runic/Resources/ProviderIcon-qwen.svg +0 -9
  234. package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +0 -4
  235. package/Sources/Runic/Resources/ProviderIcon-together.svg +0 -5
  236. package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +0 -17
  237. package/Sources/Runic/Resources/ProviderIcon-xai.svg +0 -3
  238. package/Sources/Runic/Resources/ProviderIcon-zai.svg +0 -5
  239. package/Sources/Runic/Resources/RunicMenubarIcon.svg +0 -7
  240. package/Sources/Runic/Resources/provider-context-windows.json +0 -20
  241. package/Sources/Runic/RunicApp.swift +0 -367
  242. package/Sources/Runic/SessionQuotaNotifications.swift +0 -58
  243. package/Sources/Runic/UsagePaceText.swift +0 -52
  244. package/Sources/Runic/UsageProgressBar.swift +0 -228
  245. package/Sources/Runic/Utilities/DisplayLink.swift +0 -96
  246. package/Sources/Runic/Utilities/LoadingPattern.swift +0 -62
  247. package/Sources/Runic/Utilities/PerformanceConstants.swift +0 -85
  248. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +0 -254
  249. package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +0 -22
  250. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +0 -275
  251. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +0 -377
  252. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +0 -312
  253. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +0 -311
  254. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +0 -231
  255. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +0 -228
  256. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +0 -157
  257. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +0 -63
  258. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +0 -248
  259. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +0 -104
  260. package/Sources/Runic/Views/Menu/MenuCardView.swift +0 -1889
  261. package/Sources/Runic/Views/Menu/MenuContent.swift +0 -153
  262. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +0 -371
  263. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +0 -35
  264. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +0 -241
  265. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +0 -281
  266. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +0 -214
  267. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +0 -220
  268. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +0 -86
  269. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +0 -214
  270. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +0 -398
  271. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +0 -231
  272. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +0 -369
  273. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +0 -191
  274. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +0 -132
  275. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +0 -621
  276. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +0 -306
  277. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +0 -271
  278. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +0 -268
  279. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +0 -418
  280. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +0 -637
  281. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +0 -344
  282. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +0 -230
  283. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +0 -321
  284. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +0 -469
  285. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +0 -250
  286. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +0 -50
  287. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +0 -572
  288. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +0 -291
  289. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +0 -3480
  290. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +0 -29
  291. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +0 -61
  292. package/Sources/Runic/Views/Preferences/PreferencesView.swift +0 -213
  293. package/Sources/Runic/Views/Rating/QualityRatingView.swift +0 -238
  294. package/Sources/Runic/Views/Team/TeamDashboardView.swift +0 -376
  295. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +0 -175
  296. package/Sources/Runic/Views/Team/TeamManagementView.swift +0 -762
  297. package/Sources/Runic/Views/Team/TeamMemberRow.swift +0 -181
  298. package/Sources/RunicCLI/CLIEntry.swift +0 -983
  299. package/Sources/RunicCLI/CLIRenderer.swift +0 -87
  300. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +0 -588
  301. package/Sources/RunicCLI/Commands/ModelsCommand.swift +0 -363
  302. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +0 -453
  303. package/Sources/RunicCLI/Commands/ResetCommand.swift +0 -430
  304. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +0 -167
  305. package/Sources/RunicClaudeWatchdog/main.swift +0 -122
  306. package/Sources/RunicClaudeWebProbe/main.swift +0 -62
  307. package/Sources/RunicCore/AlertRuleStore.swift +0 -187
  308. package/Sources/RunicCore/BrowserCookieImportOrder.swift +0 -25
  309. package/Sources/RunicCore/CopilotUsageModels.swift +0 -84
  310. package/Sources/RunicCore/CostUsageFetcher.swift +0 -119
  311. package/Sources/RunicCore/CostUsageModels.swift +0 -479
  312. package/Sources/RunicCore/CreditsModels.swift +0 -50
  313. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +0 -268
  314. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +0 -631
  315. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +0 -444
  316. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +0 -679
  317. package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +0 -135
  318. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +0 -173
  319. package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +0 -79
  320. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +0 -86
  321. package/Sources/RunicCore/Logging/RunicLog.swift +0 -123
  322. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +0 -370
  323. package/Sources/RunicCore/OpenAIDashboardModels.swift +0 -210
  324. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +0 -676
  325. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +0 -492
  326. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +0 -51
  327. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +0 -483
  328. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +0 -558
  329. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +0 -204
  330. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +0 -78
  331. package/Sources/RunicCore/PathEnvironment.swift +0 -482
  332. package/Sources/RunicCore/Performance/PerformanceModels.swift +0 -371
  333. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +0 -426
  334. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +0 -178
  335. package/Sources/RunicCore/ProjectBudgetStore.swift +0 -102
  336. package/Sources/RunicCore/ProviderCostSnapshot.swift +0 -29
  337. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +0 -68
  338. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +0 -685
  339. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +0 -83
  340. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +0 -142
  341. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +0 -141
  342. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +0 -188
  343. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +0 -126
  344. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +0 -164
  345. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +0 -84
  346. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +0 -75
  347. package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +0 -332
  348. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +0 -180
  349. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +0 -137
  350. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +0 -209
  351. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +0 -654
  352. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +0 -30
  353. package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +0 -480
  354. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +0 -780
  355. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +0 -147
  356. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +0 -280
  357. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +0 -100
  358. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +0 -245
  359. package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +0 -125
  360. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +0 -26
  361. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +0 -183
  362. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +0 -83
  363. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +0 -85
  364. package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +0 -123
  365. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +0 -117
  366. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +0 -100
  367. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +0 -293
  368. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +0 -385
  369. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +0 -67
  370. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +0 -595
  371. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +0 -85
  372. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +0 -108
  373. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +0 -398
  374. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +0 -67
  375. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +0 -1276
  376. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +0 -84
  377. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +0 -75
  378. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +0 -67
  379. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +0 -839
  380. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +0 -83
  381. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +0 -71
  382. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +0 -83
  383. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +0 -77
  384. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +0 -97
  385. package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +0 -54
  386. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +0 -168
  387. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +0 -911
  388. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +0 -84
  389. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +0 -75
  390. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +0 -87
  391. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +0 -216
  392. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +0 -84
  393. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +0 -75
  394. package/Sources/RunicCore/Providers/ProviderBranding.swift +0 -25
  395. package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +0 -17
  396. package/Sources/RunicCore/Providers/ProviderDescriptor.swift +0 -129
  397. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +0 -263
  398. package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +0 -98
  399. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +0 -501
  400. package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +0 -68
  401. package/Sources/RunicCore/Providers/Providers.swift +0 -184
  402. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +0 -86
  403. package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +0 -30
  404. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +0 -328
  405. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +0 -84
  406. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +0 -75
  407. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +0 -83
  408. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +0 -75
  409. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +0 -123
  410. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +0 -48
  411. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +0 -144
  412. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +0 -83
  413. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +0 -75
  414. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +0 -74
  415. package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +0 -41
  416. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +0 -745
  417. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +0 -384
  418. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +0 -307
  419. package/Sources/RunicCore/Sync/SyncProtocol.swift +0 -287
  420. package/Sources/RunicCore/Sync/SyncRecord.swift +0 -402
  421. package/Sources/RunicCore/Sync/SyncUsageExample.swift +0 -288
  422. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +0 -768
  423. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +0 -338
  424. package/Sources/RunicCore/Team/TeamModels.swift +0 -584
  425. package/Sources/RunicCore/Team/TeamShowbackExporter.swift +0 -495
  426. package/Sources/RunicCore/Team/TeamStore.swift +0 -809
  427. package/Sources/RunicCore/TextParsing.swift +0 -55
  428. package/Sources/RunicCore/UsageFetcher.swift +0 -694
  429. package/Sources/RunicCore/UsageFormatter.swift +0 -410
  430. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +0 -441
  431. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +0 -502
  432. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +0 -161
  433. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +0 -550
  434. package/Sources/RunicCore/UsageLedger/UsageLedger.swift +0 -29
  435. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +0 -675
  436. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +0 -209
  437. package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +0 -417
  438. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +0 -284
  439. package/Sources/RunicCore/UsagePace.swift +0 -81
  440. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +0 -73
  441. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +0 -74
  442. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +0 -208
  443. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +0 -455
  444. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +0 -132
  445. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +0 -589
  446. package/Sources/RunicCore/WidgetSnapshot.swift +0 -183
  447. package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +0 -14
  448. package/Sources/RunicMacros/ProviderRegistrationMacros.swift +0 -216
  449. package/Sources/RunicWidget/RunicWidgetBundle.swift +0 -79
  450. package/Sources/RunicWidget/RunicWidgetProvider.swift +0 -283
  451. package/Sources/RunicWidget/RunicWidgetViews.swift +0 -680
  452. package/Tests/RunicTests/AntigravityStatusProbeTests.swift +0 -50
  453. package/Tests/RunicTests/AppDelegateTests.swift +0 -48
  454. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +0 -21
  455. package/Tests/RunicTests/CLIArgumentParsingTests.swift +0 -69
  456. package/Tests/RunicTests/CLICostTests.swift +0 -64
  457. package/Tests/RunicTests/CLIProviderSelectionTests.swift +0 -21
  458. package/Tests/RunicTests/CLISnapshotTests.swift +0 -71
  459. package/Tests/RunicTests/CLIWebFallbackTests.swift +0 -57
  460. package/Tests/RunicTests/ClaudeOAuthTests.swift +0 -163
  461. package/Tests/RunicTests/ClaudeResilienceTests.swift +0 -29
  462. package/Tests/RunicTests/ClaudeUsageTests.swift +0 -369
  463. package/Tests/RunicTests/CodexOAuthTests.swift +0 -121
  464. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +0 -80
  465. package/Tests/RunicTests/CopilotTokenResolverTests.swift +0 -74
  466. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +0 -74
  467. package/Tests/RunicTests/CostUsageDecodingTests.swift +0 -392
  468. package/Tests/RunicTests/CostUsagePricingTests.swift +0 -48
  469. package/Tests/RunicTests/CostUsageScannerTests.swift +0 -698
  470. package/Tests/RunicTests/CursorStatusProbeTests.swift +0 -322
  471. package/Tests/RunicTests/FactoryStatusProbeTests.swift +0 -67
  472. package/Tests/RunicTests/GeminiAPITestHelpers.swift +0 -94
  473. package/Tests/RunicTests/GeminiLoginAlertTests.swift +0 -27
  474. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +0 -283
  475. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +0 -279
  476. package/Tests/RunicTests/GeminiStatusProbeTests.swift +0 -232
  477. package/Tests/RunicTests/GeminiTestEnvironment.swift +0 -87
  478. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +0 -72
  479. package/Tests/RunicTests/InstallOriginTests.swift +0 -17
  480. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +0 -9
  481. package/Tests/RunicTests/LiveAccountTests.swift +0 -24
  482. package/Tests/RunicTests/LoadingPatternTests.swift +0 -34
  483. package/Tests/RunicTests/MenuCardModelTests.swift +0 -544
  484. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +0 -11
  485. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +0 -11
  486. package/Tests/RunicTests/MiniMaxParsingTests.swift +0 -67
  487. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +0 -11
  488. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +0 -110
  489. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +0 -16
  490. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +0 -74
  491. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +0 -26
  492. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +0 -133
  493. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +0 -64
  494. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +0 -131
  495. package/Tests/RunicTests/PathBuilderTests.swift +0 -236
  496. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +0 -159
  497. package/Tests/RunicTests/ProviderIconResourcesTests.swift +0 -37
  498. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +0 -15
  499. package/Tests/RunicTests/ProviderRegistryTests.swift +0 -29
  500. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +0 -236
  501. package/Tests/RunicTests/ProviderToggleStoreTests.swift +0 -50
  502. package/Tests/RunicTests/RunicTests.swift +0 -119
  503. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +0 -35
  504. package/Tests/RunicTests/SettingsStoreTests.swift +0 -265
  505. package/Tests/RunicTests/StatusItemAnimationTests.swift +0 -54
  506. package/Tests/RunicTests/StatusMenuTests.swift +0 -475
  507. package/Tests/RunicTests/StatusProbeTests.swift +0 -376
  508. package/Tests/RunicTests/SubprocessRunnerTests.swift +0 -18
  509. package/Tests/RunicTests/SubscriptionDetectionTests.swift +0 -64
  510. package/Tests/RunicTests/TTYCommandRunnerTests.swift +0 -147
  511. package/Tests/RunicTests/TTYIntegrationTests.swift +0 -41
  512. package/Tests/RunicTests/TeamShowbackExporterTests.swift +0 -157
  513. package/Tests/RunicTests/TextParsingTests.swift +0 -11
  514. package/Tests/RunicTests/UsageFormatterTests.swift +0 -111
  515. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +0 -170
  516. package/Tests/RunicTests/UsageLedgerForecastTests.swift +0 -211
  517. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +0 -81
  518. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +0 -168
  519. package/Tests/RunicTests/UsagePaceTests.swift +0 -76
  520. package/Tests/RunicTests/UsagePaceTextTests.swift +0 -108
  521. package/Tests/RunicTests/UsagePolicyEngineTests.swift +0 -89
  522. package/Tests/RunicTests/WidgetSnapshotTests.swift +0 -43
  523. package/Tests/RunicTests/ZaiProviderTests.swift +0 -59
  524. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +0 -9
  525. package/TestsLinux/PlatformGatingTests.swift +0 -46
  526. package/appcast.xml +0 -104
  527. package/assets/logo-concept-stack.svg +0 -24
  528. package/assets/logo-minimal-bars.svg +0 -15
  529. package/assets/logo.svg +0 -20
  530. package/assets/logo2.svg +0 -43
  531. package/bin/docs-list +0 -5
  532. package/build-and-run.sh +0 -369
  533. package/docs/.nojekyll +0 -1
  534. package/docs/CNAME +0 -1
  535. package/docs/antigravity.md +0 -75
  536. package/docs/architecture.md +0 -32
  537. package/docs/claude.md +0 -93
  538. package/docs/cli-commands/integration_guide.md +0 -371
  539. package/docs/cli-commands/quick_reference.md +0 -362
  540. package/docs/cli-commands/readme.md +0 -361
  541. package/docs/cli.md +0 -156
  542. package/docs/cloudkit_sync_guide.md +0 -495
  543. package/docs/codebase_review.md +0 -288
  544. package/docs/codex-oauth.md +0 -635
  545. package/docs/codex.md +0 -104
  546. package/docs/codexbar.png +0 -0
  547. package/docs/copilot.md +0 -44
  548. package/docs/cursor.md +0 -46
  549. package/docs/design/designcomparisonchart.md +0 -510
  550. package/docs/design/implementationguide.md +0 -792
  551. package/docs/design/menubaricondesignconcepts.md +0 -796
  552. package/docs/design/mockups/hybridiconconcept.svg +0 -62
  553. package/docs/design/mockups/infinityiconconcept.svg +0 -41
  554. package/docs/design/mockups/infinityiconstates.svg +0 -129
  555. package/docs/design/mockups/wavelogorefined.svg +0 -48
  556. package/docs/design/quick_summary.md +0 -426
  557. package/docs/design/readme.md +0 -478
  558. package/docs/design/sizespecifications.md +0 -575
  559. package/docs/design-system.md +0 -836
  560. package/docs/factory.md +0 -103
  561. package/docs/gemini.md +0 -73
  562. package/docs/icon.md +0 -37
  563. package/docs/icon.png +0 -0
  564. package/docs/index.html +0 -175
  565. package/docs/keychain_fix.md +0 -274
  566. package/docs/minimax.md +0 -71
  567. package/docs/packaging.md +0 -25
  568. package/docs/progress-bar-improvements.md +0 -240
  569. package/docs/progress-bar-quick-reference.md +0 -283
  570. package/docs/progress-bar-technical-details.md +0 -432
  571. package/docs/progress-bar-visual-test.swift +0 -375
  572. package/docs/provider.md +0 -202
  573. package/docs/providers.md +0 -86
  574. package/docs/refactor/macros.md +0 -35
  575. package/docs/refresh-loop.md +0 -22
  576. package/docs/releasing-homebrew.md +0 -35
  577. package/docs/releasing.md +0 -121
  578. package/docs/research/concrete_todo_2026-02-22.md +0 -47
  579. package/docs/research/core-feature-research.md +0 -248
  580. package/docs/research/feature_uiux_roadmap_2026-02-22.md +0 -202
  581. package/docs/research/parity_matrix.md +0 -49
  582. package/docs/research/provider_expansion_candidates_2026-02-22.md +0 -80
  583. package/docs/research/provider_model_token_project_matrix_2026-02-23.md +0 -46
  584. package/docs/research/release_gate_checklist.md +0 -57
  585. package/docs/site.css +0 -505
  586. package/docs/sparkle.md +0 -28
  587. package/docs/status.md +0 -28
  588. package/docs/sync/implementation.md +0 -393
  589. package/docs/sync/readme.md +0 -317
  590. package/docs/sync_files_summary.md +0 -391
  591. package/docs/sync_quick_reference.md +0 -225
  592. package/docs/todo.md +0 -11
  593. package/docs/ui.md +0 -29
  594. package/docs/web-integration.md +0 -9
  595. package/docs/widgets.md +0 -85
  596. package/docs/workflows/error-handling-improvements.md +0 -434
  597. package/docs/workflows/folder_structure.md +0 -299
  598. package/docs/workflows/loading_states.md +0 -446
  599. package/docs/workflows/loading_states_examples.md +0 -565
  600. package/docs/workflows/loading_states_flow.md +0 -430
  601. package/docs/workflows/performance_optimizations.md +0 -457
  602. package/docs/workflows/phase1-cli-testing-report.md +0 -313
  603. package/docs/workflows/quick_reference.md +0 -228
  604. package/docs/workflows/readme.md +0 -59
  605. package/docs/workflows/recent_improvements.md +0 -275
  606. package/docs/workflows/sync_enablement_summary.md +0 -400
  607. package/docs/zai.md +0 -43
  608. package/runic.png +0 -0
  609. package/runic.svg +0 -15
  610. package/version.env +0 -2
@@ -1,1276 +0,0 @@
1
- import Foundation
2
- import Silo
3
-
4
- #if os(macOS)
5
-
6
- private let factoryCookieImportOrder: BrowserCookieImportOrder =
7
- ProviderDefaults.metadata[.factory]?.browserCookieOrder ?? Browser.defaultImportOrder
8
-
9
- // MARK: - Factory Cookie Importer
10
-
11
- /// Imports Factory session cookies from browser cookies.
12
- public enum FactoryCookieImporter {
13
- private static let cookieClient = BrowserCookieClient()
14
- private static let sessionCookieNames: Set<String> = [
15
- "wos-session",
16
- "__Secure-next-auth.session-token",
17
- "next-auth.session-token",
18
- "__Secure-authjs.session-token",
19
- "__Host-authjs.csrf-token",
20
- "authjs.session-token",
21
- "session",
22
- "access-token",
23
- ]
24
-
25
- private static let authSessionCookieNames: Set<String> = [
26
- "__Secure-next-auth.session-token",
27
- "next-auth.session-token",
28
- "__Secure-authjs.session-token",
29
- "authjs.session-token",
30
- ]
31
- private static let appBaseURL = URL(string: "https://app.factory.ai")!
32
- private static let authBaseURL = URL(string: "https://auth.factory.ai")!
33
- private static let apiBaseURL = URL(string: "https://api.factory.ai")!
34
-
35
- public struct SessionInfo: Sendable {
36
- public let cookies: [HTTPCookie]
37
- public let sourceLabel: String
38
-
39
- public init(cookies: [HTTPCookie], sourceLabel: String) {
40
- self.cookies = cookies
41
- self.sourceLabel = sourceLabel
42
- }
43
-
44
- public var cookieHeader: String {
45
- self.cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
46
- }
47
- }
48
-
49
- /// Returns all Factory sessions across supported browsers.
50
- public static func importSessions(logger: ((String) -> Void)? = nil) throws -> [SessionInfo] {
51
- let log: (String) -> Void = { msg in logger?("[factory-cookie] \(msg)") }
52
- var sessions: [SessionInfo] = []
53
-
54
- for browserSource in factoryCookieImportOrder {
55
- do {
56
- let perSource = try self.importSessions(from: browserSource, logger: logger)
57
- sessions.append(contentsOf: perSource)
58
- } catch {
59
- log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
60
- }
61
- }
62
-
63
- guard !sessions.isEmpty else {
64
- throw FactoryStatusProbeError.noSessionCookie
65
- }
66
- return sessions
67
- }
68
-
69
- public static func importSessions(
70
- from browserSource: Browser,
71
- logger: ((String) -> Void)? = nil) throws -> [SessionInfo]
72
- {
73
- let log: (String) -> Void = { msg in logger?("[factory-cookie] \(msg)") }
74
- let cookieDomains = ["factory.ai", "app.factory.ai", "auth.factory.ai"]
75
- let query = BrowserCookieQuery(domains: cookieDomains)
76
- let sources = try Self.cookieClient.records(
77
- matching: query,
78
- in: browserSource)
79
-
80
- var sessions: [SessionInfo] = []
81
- for source in sources where !source.records.isEmpty {
82
- let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)
83
- if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {
84
- log("Found \(httpCookies.count) Factory cookies in \(source.label)")
85
- log("\(source.label) cookie names: \(self.cookieNames(from: httpCookies))")
86
- if let token = httpCookies.first(where: { $0.name == "access-token" })?.value {
87
- let hint = token.contains(".") ? "jwt" : "opaque"
88
- log("\(source.label) access-token cookie: \(token.count) chars (\(hint))")
89
- }
90
- if let token = httpCookies.first(where: { self.authSessionCookieNames.contains($0.name) })?.value {
91
- let hint = token.contains(".") ? "jwt" : "opaque"
92
- log("\(source.label) session cookie: \(token.count) chars (\(hint))")
93
- }
94
- sessions.append(SessionInfo(cookies: httpCookies, sourceLabel: source.label))
95
- } else {
96
- log("\(source.label) cookies found, but no Factory session cookie present")
97
- }
98
- }
99
- return sessions
100
- }
101
-
102
- /// Attempts to import Factory cookies using the standard browser import order.
103
- public static func importSession(logger: ((String) -> Void)? = nil) throws -> SessionInfo {
104
- let sessions = try self.importSessions(logger: logger)
105
- guard let first = sessions.first else {
106
- throw FactoryStatusProbeError.noSessionCookie
107
- }
108
- return first
109
- }
110
-
111
- /// Check if Factory session cookies are available
112
- public static func hasSession(logger: ((String) -> Void)? = nil) -> Bool {
113
- do {
114
- return try !(self.importSessions(logger: logger)).isEmpty
115
- } catch {
116
- return false
117
- }
118
- }
119
-
120
- private static func cookieNames(from cookies: [HTTPCookie]) -> String {
121
- let names = Set(cookies.map { "\($0.name)@\($0.domain)" }).sorted()
122
- return names.joined(separator: ", ")
123
- }
124
- }
125
-
126
- // MARK: - Factory API Models
127
-
128
- public struct FactoryAuthResponse: Codable, Sendable {
129
- public let featureFlags: FactoryFeatureFlags?
130
- public let organization: FactoryOrganization?
131
- }
132
-
133
- public struct FactoryFeatureFlags: Codable, Sendable {
134
- public let flags: [String: Bool]?
135
- public let configs: [String: AnyCodable]?
136
- }
137
-
138
- public struct FactoryOrganization: Codable, Sendable {
139
- public let id: String?
140
- public let name: String?
141
- public let subscription: FactorySubscription?
142
-
143
- enum CodingKeys: String, CodingKey {
144
- case id
145
- case name
146
- case subscription
147
- }
148
- }
149
-
150
- public struct FactorySubscription: Codable, Sendable {
151
- public let factoryTier: String?
152
- public let orbSubscription: FactoryOrbSubscription?
153
- }
154
-
155
- public struct FactoryOrbSubscription: Codable, Sendable {
156
- public let plan: FactoryPlan?
157
- public let status: String?
158
- }
159
-
160
- public struct FactoryPlan: Codable, Sendable {
161
- public let name: String?
162
- public let id: String?
163
- }
164
-
165
- public struct FactoryUsageResponse: Codable, Sendable {
166
- public let usage: FactoryUsageData?
167
- public let source: String?
168
- public let userId: String?
169
- }
170
-
171
- public struct FactoryUsageData: Codable, Sendable {
172
- public let startDate: Int64?
173
- public let endDate: Int64?
174
- public let standard: FactoryTokenUsage?
175
- public let premium: FactoryTokenUsage?
176
- }
177
-
178
- public struct FactoryTokenUsage: Codable, Sendable {
179
- public let userTokens: Int64?
180
- public let orgTotalTokensUsed: Int64?
181
- public let totalAllowance: Int64?
182
- public let usedRatio: Double?
183
- public let orgOverageUsed: Int64?
184
- public let basicAllowance: Int64?
185
- public let orgOverageLimit: Int64?
186
- }
187
-
188
- /// Helper for encoding arbitrary JSON
189
- public struct AnyCodable: Codable, Sendable {
190
- public init(from decoder: Decoder) throws {
191
- let container = try decoder.singleValueContainer()
192
- if container.decodeNil() {
193
- return
194
- }
195
- _ = try? container.decode([String: AnyCodable].self)
196
- }
197
-
198
- public func encode(to encoder: Encoder) throws {
199
- var container = encoder.singleValueContainer()
200
- try container.encodeNil()
201
- }
202
- }
203
-
204
- // MARK: - Factory Status Snapshot
205
-
206
- public struct FactoryStatusSnapshot: Sendable {
207
- /// Standard token usage (user)
208
- public let standardUserTokens: Int64
209
- /// Standard token usage (org total)
210
- public let standardOrgTokens: Int64
211
- /// Standard token allowance
212
- public let standardAllowance: Int64
213
- /// Premium token usage (user)
214
- public let premiumUserTokens: Int64
215
- /// Premium token usage (org total)
216
- public let premiumOrgTokens: Int64
217
- /// Premium token allowance
218
- public let premiumAllowance: Int64
219
- /// Billing period start
220
- public let periodStart: Date?
221
- /// Billing period end
222
- public let periodEnd: Date?
223
- /// Plan name
224
- public let planName: String?
225
- /// Factory tier (enterprise, team, etc.)
226
- public let tier: String?
227
- /// Organization name
228
- public let organizationName: String?
229
- /// User email
230
- public let accountEmail: String?
231
- /// User ID
232
- public let userId: String?
233
- /// Raw JSON for debugging
234
- public let rawJSON: String?
235
-
236
- public init(
237
- standardUserTokens: Int64,
238
- standardOrgTokens: Int64,
239
- standardAllowance: Int64,
240
- premiumUserTokens: Int64,
241
- premiumOrgTokens: Int64,
242
- premiumAllowance: Int64,
243
- periodStart: Date?,
244
- periodEnd: Date?,
245
- planName: String?,
246
- tier: String?,
247
- organizationName: String?,
248
- accountEmail: String?,
249
- userId: String?,
250
- rawJSON: String?)
251
- {
252
- self.standardUserTokens = standardUserTokens
253
- self.standardOrgTokens = standardOrgTokens
254
- self.standardAllowance = standardAllowance
255
- self.premiumUserTokens = premiumUserTokens
256
- self.premiumOrgTokens = premiumOrgTokens
257
- self.premiumAllowance = premiumAllowance
258
- self.periodStart = periodStart
259
- self.periodEnd = periodEnd
260
- self.planName = planName
261
- self.tier = tier
262
- self.organizationName = organizationName
263
- self.accountEmail = accountEmail
264
- self.userId = userId
265
- self.rawJSON = rawJSON
266
- }
267
-
268
- /// Convert to UsageSnapshot for the common provider interface
269
- public func toUsageSnapshot() -> UsageSnapshot {
270
- // Primary: Standard tokens used (as percentage of allowance, capped reasonably)
271
- let standardPercent = self.calculateUsagePercent(
272
- used: self.standardUserTokens,
273
- allowance: self.standardAllowance)
274
-
275
- let primary = RateWindow(
276
- usedPercent: standardPercent,
277
- windowMinutes: nil,
278
- resetsAt: self.periodEnd,
279
- resetDescription: self.periodEnd.map { Self.formatResetDate($0) })
280
-
281
- // Secondary: Premium tokens used
282
- let premiumPercent = self.calculateUsagePercent(
283
- used: self.premiumUserTokens,
284
- allowance: self.premiumAllowance)
285
-
286
- let secondary = RateWindow(
287
- usedPercent: premiumPercent,
288
- windowMinutes: nil,
289
- resetsAt: self.periodEnd,
290
- resetDescription: self.periodEnd.map { Self.formatResetDate($0) })
291
-
292
- // Format login method as tier + plan
293
- let loginMethod: String? = {
294
- var parts: [String] = []
295
- if let tier = self.tier, !tier.isEmpty {
296
- parts.append("Factory \(tier.capitalized)")
297
- }
298
- if let plan = self.planName, !plan.isEmpty, !plan.lowercased().contains("factory") {
299
- parts.append(plan)
300
- }
301
- return parts.isEmpty ? nil : parts.joined(separator: " - ")
302
- }()
303
-
304
- let identity = ProviderIdentitySnapshot(
305
- providerID: .factory,
306
- accountEmail: self.accountEmail,
307
- accountOrganization: self.organizationName,
308
- loginMethod: loginMethod)
309
- return UsageSnapshot(
310
- primary: primary,
311
- secondary: secondary,
312
- tertiary: nil,
313
- providerCost: nil,
314
- updatedAt: Date(),
315
- identity: identity)
316
- }
317
-
318
- private func calculateUsagePercent(used: Int64, allowance: Int64) -> Double {
319
- // Treat very large allowances (> 1 trillion) as unlimited
320
- let unlimitedThreshold: Int64 = 1_000_000_000_000
321
- if allowance > unlimitedThreshold {
322
- // For unlimited, show a token count-based pseudo-percentage (capped at 100%)
323
- // Use 100M tokens as a reference point for "100%"
324
- let referenceTokens: Double = 100_000_000
325
- return min(100, Double(used) / referenceTokens * 100)
326
- }
327
- guard allowance > 0 else { return 0 }
328
- return min(100, Double(used) / Double(allowance) * 100)
329
- }
330
-
331
- private static func formatResetDate(_ date: Date) -> String {
332
- let formatter = DateFormatter()
333
- formatter.dateFormat = "MMM d 'at' h:mma"
334
- formatter.locale = Locale(identifier: "en_US_POSIX")
335
- return "Resets " + formatter.string(from: date)
336
- }
337
- }
338
-
339
- // MARK: - Factory Status Probe Error
340
-
341
- public enum FactoryStatusProbeError: LocalizedError, Sendable {
342
- case notLoggedIn
343
- case networkError(String)
344
- case parseFailed(String)
345
- case noSessionCookie
346
-
347
- public var errorDescription: String? {
348
- switch self {
349
- case .notLoggedIn:
350
- "Not logged in to Factory. Please log in via the Runic menu."
351
- case let .networkError(msg):
352
- "Factory API error: \(msg)"
353
- case let .parseFailed(msg):
354
- "Could not parse Factory usage: \(msg)"
355
- case .noSessionCookie:
356
- "No Factory session found. Please log in to app.factory.ai in \(factoryCookieImportOrder.loginHint)."
357
- }
358
- }
359
- }
360
-
361
- // MARK: - Factory Session Store
362
-
363
- public actor FactorySessionStore {
364
- public static let shared = FactorySessionStore()
365
-
366
- private var sessionCookies: [HTTPCookie] = []
367
- private var bearerToken: String?
368
- private var refreshToken: String?
369
- private let fileURL: URL
370
- private var didLoadFromDisk = false
371
-
372
- private init() {
373
- let fm = FileManager.default
374
- let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
375
- ?? fm.temporaryDirectory
376
- let dir = appSupport.appendingPathComponent("Runic", isDirectory: true)
377
- try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
378
- self.fileURL = dir.appendingPathComponent("factory-session.json")
379
- }
380
-
381
- public func setCookies(_ cookies: [HTTPCookie]) {
382
- self.didLoadFromDisk = true
383
- self.sessionCookies = cookies
384
- self.saveToDisk()
385
- }
386
-
387
- public func getCookies() -> [HTTPCookie] {
388
- self.loadFromDiskIfNeeded()
389
- return self.sessionCookies
390
- }
391
-
392
- public func setBearerToken(_ token: String?) {
393
- self.didLoadFromDisk = true
394
- self.bearerToken = token
395
- self.saveToDisk()
396
- }
397
-
398
- public func getBearerToken() -> String? {
399
- self.loadFromDiskIfNeeded()
400
- return self.bearerToken
401
- }
402
-
403
- public func setRefreshToken(_ token: String?) {
404
- self.didLoadFromDisk = true
405
- self.refreshToken = token
406
- self.saveToDisk()
407
- }
408
-
409
- public func getRefreshToken() -> String? {
410
- self.loadFromDiskIfNeeded()
411
- return self.refreshToken
412
- }
413
-
414
- public func clearSession() {
415
- self.didLoadFromDisk = true
416
- self.sessionCookies = []
417
- self.bearerToken = nil
418
- self.refreshToken = nil
419
- try? FileManager.default.removeItem(at: self.fileURL)
420
- }
421
-
422
- public func hasValidSession() -> Bool {
423
- self.loadFromDiskIfNeeded()
424
- return !self.sessionCookies.isEmpty || self.bearerToken != nil || self.refreshToken != nil
425
- }
426
-
427
- private func saveToDisk() {
428
- let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in
429
- guard let props = cookie.properties else { return nil }
430
- var serializable: [String: Any] = [:]
431
- for (key, value) in props {
432
- let keyString = key.rawValue
433
- if let date = value as? Date {
434
- serializable[keyString] = date.timeIntervalSince1970
435
- serializable[keyString + "_isDate"] = true
436
- } else if let url = value as? URL {
437
- serializable[keyString] = url.absoluteString
438
- serializable[keyString + "_isURL"] = true
439
- } else if JSONSerialization.isValidJSONObject([value]) ||
440
- value is String ||
441
- value is Bool ||
442
- value is NSNumber
443
- {
444
- serializable[keyString] = value
445
- }
446
- }
447
- return serializable
448
- }
449
-
450
- var payload: [String: Any] = [:]
451
- if !cookieData.isEmpty {
452
- payload["cookies"] = cookieData
453
- }
454
- if let bearerToken {
455
- payload["bearerToken"] = bearerToken
456
- }
457
- if let refreshToken {
458
- payload["refreshToken"] = refreshToken
459
- }
460
-
461
- guard !payload.isEmpty,
462
- let data = try? JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted])
463
- else {
464
- return
465
- }
466
- try? data.write(to: self.fileURL)
467
- try? FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: self.fileURL.path)
468
- }
469
-
470
- private func loadFromDisk() {
471
- guard let data = try? Data(contentsOf: self.fileURL),
472
- let json = try? JSONSerialization.jsonObject(with: data)
473
- else { return }
474
-
475
- var cookieArray: [[String: Any]] = []
476
- if let dict = json as? [String: Any] {
477
- if let stored = dict["cookies"] as? [[String: Any]] {
478
- cookieArray = stored
479
- }
480
- self.bearerToken = dict["bearerToken"] as? String
481
- self.refreshToken = dict["refreshToken"] as? String
482
- } else if let stored = json as? [[String: Any]] {
483
- cookieArray = stored
484
- }
485
-
486
- self.sessionCookies = cookieArray.compactMap { props in
487
- var cookieProps: [HTTPCookiePropertyKey: Any] = [:]
488
- for (key, value) in props {
489
- if key.hasSuffix("_isDate") || key.hasSuffix("_isURL") { continue }
490
-
491
- let propKey = HTTPCookiePropertyKey(key)
492
-
493
- if props[key + "_isDate"] as? Bool == true, let interval = value as? TimeInterval {
494
- cookieProps[propKey] = Date(timeIntervalSince1970: interval)
495
- } else if props[key + "_isURL"] as? Bool == true, let urlString = value as? String {
496
- cookieProps[propKey] = URL(string: urlString)
497
- } else {
498
- cookieProps[propKey] = value
499
- }
500
- }
501
- return HTTPCookie(properties: cookieProps)
502
- }
503
- }
504
-
505
- private func loadFromDiskIfNeeded() {
506
- guard !self.didLoadFromDisk else { return }
507
- self.didLoadFromDisk = true
508
- self.loadFromDisk()
509
- }
510
- }
511
-
512
- // MARK: - Factory Status Probe
513
-
514
- public struct FactoryStatusProbe: Sendable {
515
- public let baseURL: URL
516
- public var timeout: TimeInterval = 15.0
517
- private static let staleTokenCookieNames: Set<String> = [
518
- "access-token",
519
- "__recent_auth",
520
- ]
521
- private static let sessionCookieNames: Set<String> = [
522
- "session",
523
- "wos-session",
524
- ]
525
- private static let authSessionCookieNames: Set<String> = [
526
- "__Secure-next-auth.session-token",
527
- "next-auth.session-token",
528
- "__Secure-authjs.session-token",
529
- "authjs.session-token",
530
- ]
531
- static let appBaseURL = URL(string: "https://app.factory.ai")!
532
- static let authBaseURL = URL(string: "https://auth.factory.ai")!
533
- static let apiBaseURL = URL(string: "https://api.factory.ai")!
534
- private static let workosClientIDs = [
535
- "client_01HXRMBQ9BJ3E7QSTQ9X2PHVB7",
536
- "client_01HNM792M5G5G1A2THWPXKFMXB",
537
- ]
538
-
539
- private struct WorkOSAuthResponse: Decodable {
540
- let access_token: String
541
- let refresh_token: String?
542
- let organization_id: String?
543
- }
544
-
545
- public init(baseURL: URL = URL(string: "https://app.factory.ai")!, timeout: TimeInterval = 15.0) {
546
- self.baseURL = baseURL
547
- self.timeout = timeout
548
- }
549
-
550
- /// Fetch Factory usage using browser cookies with fallback to stored session.
551
- public func fetch(logger: ((String) -> Void)? = nil) async throws -> FactoryStatusSnapshot {
552
- let log: (String) -> Void = { msg in logger?("[factory] \(msg)") }
553
- var lastError: Error?
554
-
555
- let attempts: [FetchAttemptResult] = await [
556
- self.attemptBrowserCookies(logger: log, sources: [.safari]),
557
- self.attemptStoredCookies(logger: log),
558
- self.attemptStoredBearer(logger: log),
559
- self.attemptStoredRefreshToken(logger: log),
560
- self.attemptLocalStorageTokens(logger: log),
561
- self.attemptWorkOSCookies(logger: log, sources: [.safari]),
562
- self.attemptBrowserCookies(logger: log, sources: [.chrome, .firefox]),
563
- self.attemptWorkOSCookies(logger: log, sources: [.chrome, .firefox]),
564
- ]
565
-
566
- for result in attempts {
567
- switch result {
568
- case let .success(snapshot):
569
- return snapshot
570
- case let .failure(error):
571
- lastError = error
572
- case .skipped:
573
- continue
574
- }
575
- }
576
-
577
- if let lastError { throw lastError }
578
- throw FactoryStatusProbeError.noSessionCookie
579
- }
580
-
581
- private enum FetchAttemptResult {
582
- case success(FactoryStatusSnapshot)
583
- case failure(Error)
584
- case skipped
585
- }
586
-
587
- private func attemptBrowserCookies(
588
- logger: @escaping (String) -> Void,
589
- sources: [Browser]) async -> FetchAttemptResult
590
- {
591
- do {
592
- var lastError: Error?
593
- for browserSource in sources {
594
- let sessions = try FactoryCookieImporter.importSessions(from: browserSource, logger: logger)
595
- for session in sessions {
596
- logger("Using cookies from \(session.sourceLabel)")
597
- do {
598
- let snapshot = try await self.fetchWithCookies(session.cookies, logger: logger)
599
- await FactorySessionStore.shared.setCookies(session.cookies)
600
- return .success(snapshot)
601
- } catch {
602
- lastError = error
603
- logger("Browser session fetch failed for \(session.sourceLabel): \(error.localizedDescription)")
604
- }
605
- }
606
- }
607
- if let lastError { return .failure(lastError) }
608
- return .skipped
609
- } catch {
610
- logger("Browser cookie import failed: \(error.localizedDescription)")
611
- return .failure(error)
612
- }
613
- }
614
-
615
- private func attemptStoredCookies(logger: (String) -> Void) async -> FetchAttemptResult {
616
- let storedCookies = await FactorySessionStore.shared.getCookies()
617
- guard !storedCookies.isEmpty else { return .skipped }
618
- logger("Using stored session cookies")
619
- do {
620
- return try await .success(self.fetchWithCookies(storedCookies, logger: logger))
621
- } catch {
622
- if case FactoryStatusProbeError.notLoggedIn = error {
623
- await FactorySessionStore.shared.clearSession()
624
- logger("Stored session invalid, cleared")
625
- } else {
626
- logger("Stored session failed: \(error.localizedDescription)")
627
- }
628
- return .failure(error)
629
- }
630
- }
631
-
632
- private func attemptStoredBearer(logger: (String) -> Void) async -> FetchAttemptResult {
633
- guard let bearerToken = await FactorySessionStore.shared.getBearerToken() else { return .skipped }
634
- logger("Using stored Factory bearer token")
635
- do {
636
- return try await .success(self.fetchWithBearerToken(bearerToken, logger: logger))
637
- } catch {
638
- return .failure(error)
639
- }
640
- }
641
-
642
- private func attemptStoredRefreshToken(logger: (String) -> Void) async -> FetchAttemptResult {
643
- guard let refreshToken = await FactorySessionStore.shared.getRefreshToken(),
644
- !refreshToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
645
- else {
646
- return .skipped
647
- }
648
- logger("Using stored WorkOS refresh token")
649
- do {
650
- return try await .success(self.fetchWithWorkOSRefreshToken(
651
- refreshToken,
652
- organizationID: nil,
653
- logger: logger))
654
- } catch {
655
- if self.isInvalidGrant(error) {
656
- await FactorySessionStore.shared.setRefreshToken(nil)
657
- } else if case FactoryStatusProbeError.noSessionCookie = error {
658
- await FactorySessionStore.shared.setRefreshToken(nil)
659
- }
660
- return .failure(error)
661
- }
662
- }
663
-
664
- private func attemptLocalStorageTokens(logger: @escaping (String) -> Void) async -> FetchAttemptResult {
665
- let workosTokens = FactoryLocalStorageImporter.importWorkOSTokens(logger: logger)
666
- guard !workosTokens.isEmpty else { return .skipped }
667
- var lastError: Error?
668
- for token in workosTokens {
669
- guard !token.refreshToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
670
- continue
671
- }
672
- logger("Using WorkOS refresh token from \(token.sourceLabel)")
673
- if let accessToken = token.accessToken {
674
- do {
675
- await FactorySessionStore.shared.setBearerToken(accessToken)
676
- return try await .success(self.fetchWithBearerToken(accessToken, logger: logger))
677
- } catch {
678
- lastError = error
679
- }
680
- }
681
- do {
682
- return try await .success(self.fetchWithWorkOSRefreshToken(
683
- token.refreshToken,
684
- organizationID: token.organizationID,
685
- logger: logger))
686
- } catch {
687
- if self.isInvalidGrant(error) {
688
- await FactorySessionStore.shared.setRefreshToken(nil)
689
- }
690
- lastError = error
691
- }
692
- }
693
- if let lastError { return .failure(lastError) }
694
- return .skipped
695
- }
696
-
697
- private func attemptWorkOSCookies(
698
- logger: @escaping (String) -> Void,
699
- sources: [Browser]) async -> FetchAttemptResult
700
- {
701
- let log: (String) -> Void = { msg in logger("[factory-workos] \(msg)") }
702
- var lastError: Error?
703
-
704
- for browserSource in sources {
705
- do {
706
- let query = BrowserCookieQuery(domains: ["workos.com"])
707
- let sources = try BrowserCookieClient().records(
708
- matching: query,
709
- in: browserSource)
710
- for source in sources where !source.records.isEmpty {
711
- let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)
712
- guard !cookies.isEmpty else { continue }
713
- log("Using WorkOS cookies from \(source.label)")
714
- do {
715
- let auth = try await self.fetchWorkOSAccessTokenWithCookies(
716
- cookies: cookies,
717
- logger: logger)
718
- await FactorySessionStore.shared.setBearerToken(auth.access_token)
719
- if let refreshToken = auth.refresh_token {
720
- await FactorySessionStore.shared.setRefreshToken(refreshToken)
721
- }
722
- return try await .success(self.fetchWithBearerToken(auth.access_token, logger: logger))
723
- } catch {
724
- lastError = error
725
- log("WorkOS cookie auth failed for \(source.label): \(error.localizedDescription)")
726
- }
727
- }
728
- } catch {
729
- log("\(browserSource.displayName) WorkOS cookie import failed: \(error.localizedDescription)")
730
- lastError = error
731
- }
732
- }
733
-
734
- if let lastError { return .failure(lastError) }
735
- return .skipped
736
- }
737
-
738
- private func fetchWithWorkOSRefreshToken(
739
- _ refreshToken: String,
740
- organizationID: String?,
741
- logger: (String) -> Void) async throws -> FactoryStatusSnapshot
742
- {
743
- let auth = try await self.fetchWorkOSAccessToken(
744
- refreshToken: refreshToken,
745
- organizationID: organizationID)
746
- await FactorySessionStore.shared.setBearerToken(auth.access_token)
747
- if let newRefresh = auth.refresh_token {
748
- await FactorySessionStore.shared.setRefreshToken(newRefresh)
749
- }
750
- return try await self.fetchWithBearerToken(auth.access_token, logger: logger)
751
- }
752
-
753
- private func fetchWithCookies(
754
- _ cookies: [HTTPCookie],
755
- logger: (String) -> Void) async throws -> FactoryStatusSnapshot
756
- {
757
- let candidates = Self.baseURLCandidates(default: self.baseURL, cookies: cookies)
758
- var lastError: Error?
759
-
760
- for baseURL in candidates {
761
- if baseURL != self.baseURL {
762
- logger("Trying Factory base URL: \(baseURL.host ?? baseURL.absoluteString)")
763
- }
764
- do {
765
- return try await self.fetchWithCookies(cookies, baseURL: baseURL, logger: logger)
766
- } catch {
767
- lastError = error
768
- }
769
- }
770
-
771
- if let lastError { throw lastError }
772
- throw FactoryStatusProbeError.noSessionCookie
773
- }
774
-
775
- private func fetchWithCookies(
776
- _ cookies: [HTTPCookie],
777
- baseURL: URL,
778
- logger: (String) -> Void) async throws -> FactoryStatusSnapshot
779
- {
780
- let header = Self.cookieHeader(from: cookies)
781
- let bearerToken = Self.bearerToken(from: cookies)
782
- do {
783
- return try await self.fetchWithCookieHeader(header, bearerToken: bearerToken, baseURL: baseURL)
784
- } catch let error as FactoryStatusProbeError {
785
- if case .notLoggedIn = error, bearerToken != nil {
786
- logger("Retrying without Authorization header")
787
- return try await self.fetchWithCookieHeader(header, bearerToken: nil, baseURL: baseURL)
788
- }
789
- guard case let .networkError(message) = error,
790
- message.contains("HTTP 409")
791
- else {
792
- throw error
793
- }
794
-
795
- var lastError: Error? = error
796
- if bearerToken != nil {
797
- logger("Retrying without Authorization header (HTTP 409)")
798
- do {
799
- return try await self.fetchWithCookieHeader(header, bearerToken: nil, baseURL: baseURL)
800
- } catch {
801
- lastError = error
802
- }
803
- }
804
-
805
- let retries: [(String, (HTTPCookie) -> Bool)] = [
806
- ("Retrying without access-token cookies", { !Self.staleTokenCookieNames.contains($0.name) }),
807
- ("Retrying without session cookies", { !Self.sessionCookieNames.contains($0.name) }),
808
- ("Retrying without access-token/session cookies", {
809
- !Self.staleTokenCookieNames.contains($0.name) && !Self.sessionCookieNames.contains($0.name)
810
- }),
811
- ]
812
-
813
- for (label, predicate) in retries {
814
- let filtered = cookies.filter(predicate)
815
- guard filtered.count < cookies.count else { continue }
816
- logger(label)
817
- do {
818
- let filteredBearer = Self.bearerToken(from: filtered)
819
- return try await self.fetchWithCookieHeader(
820
- Self.cookieHeader(from: filtered),
821
- bearerToken: filteredBearer,
822
- baseURL: baseURL)
823
- } catch let retryError as FactoryStatusProbeError {
824
- switch retryError {
825
- case let .networkError(retryMessage)
826
- where retryMessage.contains("HTTP 409") &&
827
- retryMessage.localizedCaseInsensitiveContains("stale token"):
828
- lastError = retryError
829
- continue
830
- case .notLoggedIn:
831
- lastError = retryError
832
- continue
833
- default:
834
- throw retryError
835
- }
836
- }
837
- }
838
-
839
- let authOnly = cookies.filter {
840
- Self.authSessionCookieNames.contains($0.name) || $0.name == "__Host-authjs.csrf-token"
841
- }
842
- if !authOnly.isEmpty, authOnly.count < cookies.count {
843
- logger("Retrying with auth session cookies only")
844
- do {
845
- return try await self.fetchWithCookieHeader(
846
- Self.cookieHeader(from: authOnly),
847
- bearerToken: Self.bearerToken(from: authOnly),
848
- baseURL: baseURL)
849
- } catch let retryError as FactoryStatusProbeError {
850
- lastError = retryError
851
- }
852
- }
853
-
854
- if let lastError { throw lastError }
855
- throw error
856
- } catch {
857
- throw error
858
- }
859
- }
860
-
861
- private static func cookieHeader(from cookies: [HTTPCookie]) -> String {
862
- cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
863
- }
864
-
865
- private func fetchWithCookieHeader(
866
- _ cookieHeader: String,
867
- bearerToken: String?,
868
- baseURL: URL) async throws -> FactoryStatusSnapshot
869
- {
870
- // First fetch auth info to get user ID and org info
871
- let authInfo = try await self.fetchAuthInfo(
872
- cookieHeader: cookieHeader,
873
- bearerToken: bearerToken,
874
- baseURL: baseURL)
875
-
876
- // Extract user ID from JWT in the auth response or use a default endpoint
877
- let userId = self.extractUserIdFromAuth(authInfo)
878
-
879
- // Fetch usage data
880
- let usageData = try await self.fetchUsage(
881
- cookieHeader: cookieHeader,
882
- bearerToken: bearerToken,
883
- userId: userId,
884
- baseURL: baseURL)
885
-
886
- return self.buildSnapshot(authInfo: authInfo, usageData: usageData, userId: userId)
887
- }
888
-
889
- private func fetchAuthInfo(
890
- cookieHeader: String,
891
- bearerToken: String?,
892
- baseURL: URL) async throws -> FactoryAuthResponse
893
- {
894
- let url = baseURL.appendingPathComponent("/api/app/auth/me")
895
- var request = URLRequest(url: url)
896
- request.timeoutInterval = self.timeout
897
- request.httpMethod = "GET"
898
- request.setValue("application/json", forHTTPHeaderField: "Accept")
899
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
900
- request.setValue("https://app.factory.ai", forHTTPHeaderField: "Origin")
901
- request.setValue("https://app.factory.ai/", forHTTPHeaderField: "Referer")
902
- if !cookieHeader.isEmpty {
903
- request.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
904
- }
905
- request.setValue("web-app", forHTTPHeaderField: "x-factory-client")
906
- if let bearerToken {
907
- request.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
908
- }
909
-
910
- let (data, response) = try await URLSession.shared.data(for: request)
911
-
912
- guard let httpResponse = response as? HTTPURLResponse else {
913
- throw FactoryStatusProbeError.networkError("Invalid response")
914
- }
915
-
916
- if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {
917
- throw FactoryStatusProbeError.notLoggedIn
918
- }
919
-
920
- guard httpResponse.statusCode == 200 else {
921
- let body = String(data: data, encoding: .utf8)?
922
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "<binary>"
923
- let snippet = body.isEmpty ? "" : ": \(body.prefix(200))"
924
- throw FactoryStatusProbeError.networkError("HTTP \(httpResponse.statusCode)\(snippet)")
925
- }
926
-
927
- do {
928
- let decoder = JSONDecoder()
929
- return try decoder.decode(FactoryAuthResponse.self, from: data)
930
- } catch {
931
- let rawJSON = String(data: data, encoding: .utf8) ?? "<binary>"
932
- throw FactoryStatusProbeError
933
- .parseFailed("Auth decode failed: \(error.localizedDescription). Raw: \(rawJSON.prefix(200))")
934
- }
935
- }
936
-
937
- private func fetchUsage(
938
- cookieHeader: String,
939
- bearerToken: String?,
940
- userId: String?,
941
- baseURL: URL) async throws -> FactoryUsageResponse
942
- {
943
- let url = baseURL.appendingPathComponent("/api/organization/subscription/usage")
944
- var request = URLRequest(url: url)
945
- request.timeoutInterval = self.timeout
946
- request.httpMethod = "POST"
947
- request.setValue("application/json", forHTTPHeaderField: "Accept")
948
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
949
- request.setValue("https://app.factory.ai", forHTTPHeaderField: "Origin")
950
- request.setValue("https://app.factory.ai/", forHTTPHeaderField: "Referer")
951
- if !cookieHeader.isEmpty {
952
- request.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
953
- }
954
- request.setValue("web-app", forHTTPHeaderField: "x-factory-client")
955
- if let bearerToken {
956
- request.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
957
- }
958
-
959
- // Build request body
960
- var body: [String: Any] = ["useCache": true]
961
- if let userId {
962
- body["userId"] = userId
963
- }
964
- request.httpBody = try? JSONSerialization.data(withJSONObject: body)
965
-
966
- let (data, response) = try await URLSession.shared.data(for: request)
967
-
968
- guard let httpResponse = response as? HTTPURLResponse else {
969
- throw FactoryStatusProbeError.networkError("Invalid response")
970
- }
971
-
972
- if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {
973
- throw FactoryStatusProbeError.notLoggedIn
974
- }
975
-
976
- guard httpResponse.statusCode == 200 else {
977
- let body = String(data: data, encoding: .utf8)?
978
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "<binary>"
979
- let snippet = body.isEmpty ? "" : ": \(body.prefix(200))"
980
- throw FactoryStatusProbeError.networkError("HTTP \(httpResponse.statusCode)\(snippet)")
981
- }
982
-
983
- do {
984
- let decoder = JSONDecoder()
985
- return try decoder.decode(FactoryUsageResponse.self, from: data)
986
- } catch {
987
- let rawJSON = String(data: data, encoding: .utf8) ?? "<binary>"
988
- throw FactoryStatusProbeError
989
- .parseFailed("Usage decode failed: \(error.localizedDescription). Raw: \(rawJSON.prefix(200))")
990
- }
991
- }
992
-
993
- private static func baseURLCandidates(default baseURL: URL, cookies: [HTTPCookie]) -> [URL] {
994
- let cookieDomains = Set(
995
- cookies.map {
996
- $0.domain.trimmingCharacters(in: CharacterSet(charactersIn: "."))
997
- })
998
-
999
- var candidates: [URL] = []
1000
- if cookieDomains.contains("auth.factory.ai") {
1001
- candidates.append(Self.authBaseURL)
1002
- }
1003
- candidates.append(Self.apiBaseURL)
1004
- candidates.append(Self.appBaseURL)
1005
- candidates.append(baseURL)
1006
-
1007
- var seen = Set<String>()
1008
- return candidates.filter { url in
1009
- let key = url.absoluteString
1010
- if seen.contains(key) { return false }
1011
- seen.insert(key)
1012
- return true
1013
- }
1014
- }
1015
-
1016
- private static func bearerToken(from cookies: [HTTPCookie]) -> String? {
1017
- let accessToken = cookies.first(where: { $0.name == "access-token" })?.value
1018
- let sessionToken = cookies.first(where: { Self.authSessionCookieNames.contains($0.name) })?.value
1019
- let legacySession = cookies.first(where: { $0.name == "session" })?.value
1020
-
1021
- if let accessToken, accessToken.contains(".") {
1022
- return accessToken
1023
- }
1024
- if let sessionToken, sessionToken.contains(".") {
1025
- return sessionToken
1026
- }
1027
- if let legacySession, legacySession.contains(".") {
1028
- return legacySession
1029
- }
1030
- return accessToken ?? sessionToken
1031
- }
1032
-
1033
- private func fetchWithBearerToken(
1034
- _ bearerToken: String,
1035
- logger: (String) -> Void) async throws -> FactoryStatusSnapshot
1036
- {
1037
- let candidates = [Self.apiBaseURL, self.baseURL]
1038
- var lastError: Error?
1039
- for baseURL in candidates {
1040
- if baseURL != Self.apiBaseURL {
1041
- logger("Trying Factory bearer base URL: \(baseURL.host ?? baseURL.absoluteString)")
1042
- }
1043
- do {
1044
- return try await self.fetchWithCookieHeader(
1045
- "",
1046
- bearerToken: bearerToken,
1047
- baseURL: baseURL)
1048
- } catch {
1049
- lastError = error
1050
- }
1051
- }
1052
- if let lastError { throw lastError }
1053
- throw FactoryStatusProbeError.notLoggedIn
1054
- }
1055
-
1056
- private func fetchWorkOSAccessToken(
1057
- refreshToken: String,
1058
- organizationID: String?) async throws -> WorkOSAuthResponse
1059
- {
1060
- var lastError: Error?
1061
- for clientID in Self.workosClientIDs {
1062
- do {
1063
- return try await self.fetchWorkOSAccessToken(
1064
- refreshToken: refreshToken,
1065
- organizationID: organizationID,
1066
- clientID: clientID)
1067
- } catch {
1068
- lastError = error
1069
- }
1070
- }
1071
- if let lastError { throw lastError }
1072
- throw FactoryStatusProbeError.networkError("WorkOS auth failed")
1073
- }
1074
-
1075
- private func fetchWorkOSAccessToken(
1076
- refreshToken: String,
1077
- organizationID: String?,
1078
- clientID: String) async throws -> WorkOSAuthResponse
1079
- {
1080
- guard let url = URL(string: "https://api.workos.com/user_management/authenticate") else {
1081
- throw FactoryStatusProbeError.networkError("WorkOS auth URL unavailable")
1082
- }
1083
-
1084
- var request = URLRequest(url: url)
1085
- request.timeoutInterval = self.timeout
1086
- request.httpMethod = "POST"
1087
- request.setValue("application/json", forHTTPHeaderField: "Accept")
1088
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
1089
-
1090
- var body: [String: Any] = [
1091
- "client_id": clientID,
1092
- "grant_type": "refresh_token",
1093
- "refresh_token": refreshToken,
1094
- ]
1095
- if let organizationID {
1096
- body["organization_id"] = organizationID
1097
- }
1098
- request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
1099
-
1100
- let (data, response) = try await URLSession.shared.data(for: request)
1101
- guard let httpResponse = response as? HTTPURLResponse else {
1102
- throw FactoryStatusProbeError.networkError("Invalid WorkOS response")
1103
- }
1104
-
1105
- guard httpResponse.statusCode == 200 else {
1106
- if httpResponse.statusCode == 400, Self.isMissingWorkOSRefreshToken(data) {
1107
- throw FactoryStatusProbeError.noSessionCookie
1108
- }
1109
- let body = String(data: data, encoding: .utf8)?
1110
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "<binary>"
1111
- let snippet = body.isEmpty ? "" : ": \(body.prefix(200))"
1112
- throw FactoryStatusProbeError.networkError("WorkOS HTTP \(httpResponse.statusCode)\(snippet)")
1113
- }
1114
-
1115
- let decoder = JSONDecoder()
1116
- return try decoder.decode(WorkOSAuthResponse.self, from: data)
1117
- }
1118
-
1119
- private func fetchWorkOSAccessTokenWithCookies(
1120
- cookies: [HTTPCookie],
1121
- logger: (String) -> Void) async throws -> WorkOSAuthResponse
1122
- {
1123
- let cookieHeader = Self.cookieHeader(from: cookies)
1124
- guard !cookieHeader.isEmpty else {
1125
- throw FactoryStatusProbeError.networkError("Missing WorkOS cookies")
1126
- }
1127
-
1128
- var lastError: Error?
1129
- for clientID in Self.workosClientIDs {
1130
- do {
1131
- return try await self.fetchWorkOSAccessTokenWithCookies(
1132
- cookieHeader: cookieHeader,
1133
- organizationID: nil,
1134
- clientID: clientID)
1135
- } catch {
1136
- lastError = error
1137
- logger("WorkOS cookie auth failed for client \(clientID): \(error.localizedDescription)")
1138
- }
1139
- }
1140
- if let lastError { throw lastError }
1141
- throw FactoryStatusProbeError.networkError("WorkOS cookie auth failed")
1142
- }
1143
-
1144
- private func fetchWorkOSAccessTokenWithCookies(
1145
- cookieHeader: String,
1146
- organizationID: String?,
1147
- clientID: String) async throws -> WorkOSAuthResponse
1148
- {
1149
- guard let url = URL(string: "https://api.workos.com/user_management/authenticate") else {
1150
- throw FactoryStatusProbeError.networkError("WorkOS auth URL unavailable")
1151
- }
1152
-
1153
- var request = URLRequest(url: url)
1154
- request.timeoutInterval = self.timeout
1155
- request.httpMethod = "POST"
1156
- request.setValue("application/json", forHTTPHeaderField: "Accept")
1157
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
1158
- request.setValue(cookieHeader, forHTTPHeaderField: "Cookie")
1159
-
1160
- var body: [String: Any] = [
1161
- "client_id": clientID,
1162
- "grant_type": "refresh_token",
1163
- "useCookie": true,
1164
- ]
1165
- if let organizationID {
1166
- body["organization_id"] = organizationID
1167
- }
1168
- request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
1169
-
1170
- let (data, response) = try await URLSession.shared.data(for: request)
1171
- guard let httpResponse = response as? HTTPURLResponse else {
1172
- throw FactoryStatusProbeError.networkError("Invalid WorkOS response")
1173
- }
1174
-
1175
- guard httpResponse.statusCode == 200 else {
1176
- if httpResponse.statusCode == 400, Self.isMissingWorkOSRefreshToken(data) {
1177
- throw FactoryStatusProbeError.noSessionCookie
1178
- }
1179
- let bodyText = String(data: data, encoding: .utf8)?
1180
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? "<binary>"
1181
- let snippet = bodyText.isEmpty ? "" : ": \(bodyText.prefix(200))"
1182
- throw FactoryStatusProbeError.networkError("WorkOS HTTP \(httpResponse.statusCode)\(snippet)")
1183
- }
1184
-
1185
- let decoder = JSONDecoder()
1186
- return try decoder.decode(WorkOSAuthResponse.self, from: data)
1187
- }
1188
-
1189
- private func isInvalidGrant(_ error: Error) -> Bool {
1190
- guard case let FactoryStatusProbeError.networkError(message) = error else {
1191
- return false
1192
- }
1193
- return message.localizedCaseInsensitiveContains("invalid_grant")
1194
- }
1195
-
1196
- static func isMissingWorkOSRefreshToken(_ data: Data) -> Bool {
1197
- guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
1198
- return false
1199
- }
1200
- guard let description = json["error_description"] as? String else { return false }
1201
- return description.localizedCaseInsensitiveContains("missing refresh token")
1202
- }
1203
-
1204
- private func extractUserIdFromAuth(_ auth: FactoryAuthResponse) -> String? {
1205
- // The user ID might be in the organization or we might need to parse JWT
1206
- // For now, return nil and let the API handle it
1207
- nil
1208
- }
1209
-
1210
- private func buildSnapshot(
1211
- authInfo: FactoryAuthResponse,
1212
- usageData: FactoryUsageResponse,
1213
- userId: String?) -> FactoryStatusSnapshot
1214
- {
1215
- let usage = usageData.usage
1216
-
1217
- let periodStart: Date? = usage?.startDate.map { Date(timeIntervalSince1970: TimeInterval($0) / 1000) }
1218
- let periodEnd: Date? = usage?.endDate.map { Date(timeIntervalSince1970: TimeInterval($0) / 1000) }
1219
-
1220
- return FactoryStatusSnapshot(
1221
- standardUserTokens: usage?.standard?.userTokens ?? 0,
1222
- standardOrgTokens: usage?.standard?.orgTotalTokensUsed ?? 0,
1223
- standardAllowance: usage?.standard?.totalAllowance ?? 0,
1224
- premiumUserTokens: usage?.premium?.userTokens ?? 0,
1225
- premiumOrgTokens: usage?.premium?.orgTotalTokensUsed ?? 0,
1226
- premiumAllowance: usage?.premium?.totalAllowance ?? 0,
1227
- periodStart: periodStart,
1228
- periodEnd: periodEnd,
1229
- planName: authInfo.organization?.subscription?.orbSubscription?.plan?.name,
1230
- tier: authInfo.organization?.subscription?.factoryTier,
1231
- organizationName: authInfo.organization?.name,
1232
- accountEmail: nil, // Email is in JWT, not in auth response body
1233
- userId: userId ?? usageData.userId,
1234
- rawJSON: nil)
1235
- }
1236
- }
1237
-
1238
- #else
1239
-
1240
- // MARK: - Factory (Unsupported)
1241
-
1242
- public enum FactoryStatusProbeError: LocalizedError, Sendable {
1243
- case notSupported
1244
-
1245
- public var errorDescription: String? {
1246
- "Factory is only supported on macOS."
1247
- }
1248
- }
1249
-
1250
- public struct FactoryStatusSnapshot: Sendable {
1251
- public init() {}
1252
-
1253
- public func toUsageSnapshot() -> UsageSnapshot {
1254
- UsageSnapshot(
1255
- primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
1256
- secondary: nil,
1257
- tertiary: nil,
1258
- providerCost: nil,
1259
- updatedAt: Date(),
1260
- identity: nil)
1261
- }
1262
- }
1263
-
1264
- public struct FactoryStatusProbe: Sendable {
1265
- public init(baseURL: URL = URL(string: "https://app.factory.ai")!, timeout: TimeInterval = 15.0) {
1266
- _ = baseURL
1267
- _ = timeout
1268
- }
1269
-
1270
- public func fetch(logger: ((String) -> Void)? = nil) async throws -> FactoryStatusSnapshot {
1271
- _ = logger
1272
- throw FactoryStatusProbeError.notSupported
1273
- }
1274
- }
1275
-
1276
- #endif