@sriinnu/runic 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (584) hide show
  1. package/.swiftformat +50 -0
  2. package/.swiftlint.yml +160 -0
  3. package/CHANGELOG.md +307 -0
  4. package/CONTRIBUTING.md +456 -0
  5. package/Icon.icns +0 -0
  6. package/Icon.icon/Assets/runic.png +0 -0
  7. package/Icon.icon/icon.json +36 -0
  8. package/LICENSE +21 -0
  9. package/Package.resolved +51 -0
  10. package/Package.swift +136 -0
  11. package/Packages/Helix/LICENSE +21 -0
  12. package/Packages/Helix/Package.swift +29 -0
  13. package/Packages/Helix/README.md +976 -0
  14. package/Packages/Helix/Sources/Helix/Binding.swift +111 -0
  15. package/Packages/Helix/Sources/Helix/CommandParser.swift +160 -0
  16. package/Packages/Helix/Sources/Helix/CommandSignature.swift +117 -0
  17. package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +72 -0
  18. package/Packages/Helix/Sources/Helix/HelpGenerator.swift +175 -0
  19. package/Packages/Helix/Sources/Helix/NameSpecification.swift +98 -0
  20. package/Packages/Helix/Sources/Helix/ParsableCommand.swift +204 -0
  21. package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +44 -0
  22. package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +34 -0
  23. package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +91 -0
  24. package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +94 -0
  25. package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +167 -0
  26. package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +99 -0
  27. package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +146 -0
  28. package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +97 -0
  29. package/Packages/Helix/Sources/Helix/Program.swift +204 -0
  30. package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +367 -0
  31. package/Packages/Silo/LICENSE +21 -0
  32. package/Packages/Silo/Package.swift +34 -0
  33. package/Packages/Silo/README.md +466 -0
  34. package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +169 -0
  35. package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +365 -0
  36. package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +594 -0
  37. package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +326 -0
  38. package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +154 -0
  39. package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +119 -0
  40. package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +121 -0
  41. package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +391 -0
  42. package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +240 -0
  43. package/Packages/Silo/Sources/Silo/CryptoSupport.swift +86 -0
  44. package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +172 -0
  45. package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +172 -0
  46. package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +105 -0
  47. package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +191 -0
  48. package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +41 -0
  49. package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +110 -0
  50. package/README.md +109 -0
  51. package/RuniciOS/App/RuniciOSApp.swift +105 -0
  52. package/RuniciOS/Core/iOSUsageStore.swift +192 -0
  53. package/RuniciOS/Views/ProviderDetailView.swift +312 -0
  54. package/RuniciOS/Views/ProviderListView.swift +228 -0
  55. package/RuniciOS/Widgets/LargeWidget.swift +367 -0
  56. package/RuniciOS/Widgets/LiveActivity.swift +342 -0
  57. package/RuniciOS/Widgets/LockScreenWidget.swift +273 -0
  58. package/RuniciOS/Widgets/MediumWidget.swift +370 -0
  59. package/RuniciOS/Widgets/RunicWidget.swift +274 -0
  60. package/RuniciOS/Widgets/SmallWidget.swift +281 -0
  61. package/RuniciOS/Widgets/WidgetModels.swift +285 -0
  62. package/RuniciOS/Widgets/WidgetTimeline.swift +270 -0
  63. package/Scripts/CREATION-SUMMARY.md +367 -0
  64. package/Scripts/DIRECTORY-STRUCTURE.md +358 -0
  65. package/Scripts/GETTING-STARTED-CHECKLIST.md +364 -0
  66. package/Scripts/INDEX.md +365 -0
  67. package/Scripts/QUICK-REFERENCE.md +164 -0
  68. package/Scripts/README-SCRIPTS.md +926 -0
  69. package/Scripts/build-android.sh +526 -0
  70. package/Scripts/build-ios.sh +529 -0
  71. package/Scripts/build-macos.sh +603 -0
  72. package/Scripts/build-windows.sh +504 -0
  73. package/Scripts/build_icon.sh +49 -0
  74. package/Scripts/changelog-to-html.sh +89 -0
  75. package/Scripts/check-release-assets.sh +10 -0
  76. package/Scripts/ci-config.yml +474 -0
  77. package/Scripts/compile_and_run.sh +219 -0
  78. package/Scripts/compile_and_run_adhoc.sh +4 -0
  79. package/Scripts/docs-list.mjs +119 -0
  80. package/Scripts/make_appcast.sh +58 -0
  81. package/Scripts/package_app.sh +409 -0
  82. package/Scripts/release-lib.sh +214 -0
  83. package/Scripts/release.sh +76 -0
  84. package/Scripts/setup-all.sh +387 -0
  85. package/Scripts/setup-ios.sh +404 -0
  86. package/Scripts/setup-react-native.sh +523 -0
  87. package/Scripts/sign-and-notarize.sh +122 -0
  88. package/Scripts/test_live_update.sh +38 -0
  89. package/Scripts/validate_changelog.sh +23 -0
  90. package/Scripts/verify_appcast.sh +92 -0
  91. package/Sources/Runic/About.swift +93 -0
  92. package/Sources/Runic/AppNotifications.swift +115 -0
  93. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +657 -0
  94. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +518 -0
  95. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +810 -0
  96. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +87 -0
  97. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +1379 -0
  98. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +337 -0
  99. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +395 -0
  100. package/Sources/Runic/Controllers/StatusItemController.swift +360 -0
  101. package/Sources/Runic/Core/BudgetNotificationManager.swift +80 -0
  102. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +103 -0
  103. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +154 -0
  104. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +206 -0
  105. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +95 -0
  106. package/Sources/Runic/Core/Rendering/IconRenderer.swift +1216 -0
  107. package/Sources/Runic/Core/Rendering/IconView.swift +120 -0
  108. package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +71 -0
  109. package/Sources/Runic/Core/RunicAnimation.swift +53 -0
  110. package/Sources/Runic/Core/RunicColors.swift +73 -0
  111. package/Sources/Runic/Core/RunicCornerRadius.swift +15 -0
  112. package/Sources/Runic/Core/RunicSpacing.swift +68 -0
  113. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +143 -0
  114. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +143 -0
  115. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +143 -0
  116. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +143 -0
  117. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +143 -0
  118. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +158 -0
  119. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +143 -0
  120. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +143 -0
  121. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +143 -0
  122. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +143 -0
  123. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +143 -0
  124. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +143 -0
  125. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +143 -0
  126. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +143 -0
  127. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +143 -0
  128. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +143 -0
  129. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +30 -0
  130. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +143 -0
  131. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +143 -0
  132. package/Sources/Runic/Core/Stores/SettingsStore.swift +1702 -0
  133. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +143 -0
  134. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +20 -0
  135. package/Sources/Runic/Core/Stores/UsageStore+Status.swift +189 -0
  136. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +33 -0
  137. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +67 -0
  138. package/Sources/Runic/Core/Stores/UsageStore.swift +3564 -0
  139. package/Sources/Runic/Core/Stores/XAITokenStore.swift +143 -0
  140. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +143 -0
  141. package/Sources/Runic/Core/UsageExporter.swift +193 -0
  142. package/Sources/Runic/Date+RelativeDescription.swift +21 -0
  143. package/Sources/Runic/HiddenWindowView.swift +32 -0
  144. package/Sources/Runic/InstallOrigin.swift +9 -0
  145. package/Sources/Runic/KeyboardShortcuts+Names.swift +6 -0
  146. package/Sources/Runic/MouseLocationReader.swift +65 -0
  147. package/Sources/Runic/Notifications+Runic.swift +6 -0
  148. package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +503 -0
  149. package/Sources/Runic/ProviderRegistry.swift +100 -0
  150. package/Sources/Runic/ProviderSwitcherButtons.swift +267 -0
  151. package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +11 -0
  152. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +14 -0
  153. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +24 -0
  154. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +52 -0
  155. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +42 -0
  156. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +24 -0
  157. package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +26 -0
  158. package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +14 -0
  159. package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +18 -0
  160. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +15 -0
  161. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +24 -0
  162. package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +193 -0
  163. package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +110 -0
  164. package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +28 -0
  165. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +15 -0
  166. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +24 -0
  167. package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +12 -0
  168. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +15 -0
  169. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +24 -0
  170. package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +20 -0
  171. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +15 -0
  172. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +24 -0
  173. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +24 -0
  174. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +32 -0
  175. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +24 -0
  176. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +24 -0
  177. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +24 -0
  178. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +24 -0
  179. package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +15 -0
  180. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +48 -0
  181. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +66 -0
  182. package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +12 -0
  183. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +99 -0
  184. package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +18 -0
  185. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +24 -0
  186. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +24 -0
  187. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +24 -0
  188. package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +2 -0
  189. package/Sources/Runic/Resources/ProviderIcon-auggie.svg +3 -0
  190. package/Sources/Runic/Resources/ProviderIcon-azure.svg +5 -0
  191. package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +7 -0
  192. package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +3 -0
  193. package/Sources/Runic/Resources/ProviderIcon-claude.svg +1 -0
  194. package/Sources/Runic/Resources/ProviderIcon-codex.svg +1 -0
  195. package/Sources/Runic/Resources/ProviderIcon-cohere.svg +3 -0
  196. package/Sources/Runic/Resources/ProviderIcon-copilot.svg +1 -0
  197. package/Sources/Runic/Resources/ProviderIcon-cursor.svg +3 -0
  198. package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +4 -0
  199. package/Sources/Runic/Resources/ProviderIcon-factory.svg +3 -0
  200. package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +3 -0
  201. package/Sources/Runic/Resources/ProviderIcon-gemini.svg +1 -0
  202. package/Sources/Runic/Resources/ProviderIcon-groq.svg +1 -0
  203. package/Sources/Runic/Resources/ProviderIcon-kimi.svg +4 -0
  204. package/Sources/Runic/Resources/ProviderIcon-minimax.svg +1 -0
  205. package/Sources/Runic/Resources/ProviderIcon-mistral.svg +5 -0
  206. package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +7 -0
  207. package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +4 -0
  208. package/Sources/Runic/Resources/ProviderIcon-qwen.svg +9 -0
  209. package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +4 -0
  210. package/Sources/Runic/Resources/ProviderIcon-together.svg +5 -0
  211. package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +17 -0
  212. package/Sources/Runic/Resources/ProviderIcon-xai.svg +3 -0
  213. package/Sources/Runic/Resources/ProviderIcon-zai.svg +5 -0
  214. package/Sources/Runic/Resources/RunicMenubarIcon.svg +7 -0
  215. package/Sources/Runic/RunicApp.swift +357 -0
  216. package/Sources/Runic/SessionQuotaNotifications.swift +58 -0
  217. package/Sources/Runic/UsagePaceText.swift +52 -0
  218. package/Sources/Runic/UsageProgressBar.swift +224 -0
  219. package/Sources/Runic/Utilities/DisplayLink.swift +96 -0
  220. package/Sources/Runic/Utilities/LoadingPattern.swift +60 -0
  221. package/Sources/Runic/Utilities/PerformanceConstants.swift +85 -0
  222. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +256 -0
  223. package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +22 -0
  224. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +275 -0
  225. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +377 -0
  226. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +312 -0
  227. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +312 -0
  228. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +228 -0
  229. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +228 -0
  230. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +162 -0
  231. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +64 -0
  232. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +268 -0
  233. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +104 -0
  234. package/Sources/Runic/Views/Menu/MenuCardView.swift +1900 -0
  235. package/Sources/Runic/Views/Menu/MenuContent.swift +150 -0
  236. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +369 -0
  237. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +42 -0
  238. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +235 -0
  239. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +141 -0
  240. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +209 -0
  241. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +220 -0
  242. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +88 -0
  243. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +201 -0
  244. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +398 -0
  245. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +232 -0
  246. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +417 -0
  247. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +190 -0
  248. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +132 -0
  249. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +618 -0
  250. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +305 -0
  251. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +271 -0
  252. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +268 -0
  253. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +422 -0
  254. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +641 -0
  255. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +344 -0
  256. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +230 -0
  257. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +322 -0
  258. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +469 -0
  259. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +231 -0
  260. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +50 -0
  261. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +497 -0
  262. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +288 -0
  263. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +3435 -0
  264. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +25 -0
  265. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +59 -0
  266. package/Sources/Runic/Views/Preferences/PreferencesView.swift +206 -0
  267. package/Sources/Runic/Views/Rating/QualityRatingView.swift +238 -0
  268. package/Sources/Runic/Views/Team/TeamDashboardView.swift +371 -0
  269. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +175 -0
  270. package/Sources/Runic/Views/Team/TeamManagementView.swift +761 -0
  271. package/Sources/Runic/Views/Team/TeamMemberRow.swift +181 -0
  272. package/Sources/RunicCLI/CLIEntry.swift +942 -0
  273. package/Sources/RunicCLI/CLIRenderer.swift +87 -0
  274. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +588 -0
  275. package/Sources/RunicCLI/Commands/ModelsCommand.swift +362 -0
  276. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +439 -0
  277. package/Sources/RunicCLI/Commands/ResetCommand.swift +429 -0
  278. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +168 -0
  279. package/Sources/RunicClaudeWatchdog/main.swift +122 -0
  280. package/Sources/RunicClaudeWebProbe/main.swift +62 -0
  281. package/Sources/RunicCore/AlertRuleStore.swift +187 -0
  282. package/Sources/RunicCore/BrowserCookieImportOrder.swift +25 -0
  283. package/Sources/RunicCore/CopilotUsageModels.swift +84 -0
  284. package/Sources/RunicCore/CostUsageFetcher.swift +119 -0
  285. package/Sources/RunicCore/CostUsageModels.swift +479 -0
  286. package/Sources/RunicCore/CreditsModels.swift +50 -0
  287. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +269 -0
  288. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +622 -0
  289. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +500 -0
  290. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +679 -0
  291. package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +135 -0
  292. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +173 -0
  293. package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +79 -0
  294. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +86 -0
  295. package/Sources/RunicCore/Logging/RunicLog.swift +123 -0
  296. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +371 -0
  297. package/Sources/RunicCore/OpenAIDashboardModels.swift +210 -0
  298. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +676 -0
  299. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +492 -0
  300. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +51 -0
  301. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +483 -0
  302. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +558 -0
  303. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +204 -0
  304. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +78 -0
  305. package/Sources/RunicCore/PathEnvironment.swift +482 -0
  306. package/Sources/RunicCore/Performance/PerformanceModels.swift +371 -0
  307. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +409 -0
  308. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +178 -0
  309. package/Sources/RunicCore/ProjectBudgetStore.swift +99 -0
  310. package/Sources/RunicCore/ProviderCostSnapshot.swift +29 -0
  311. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +66 -0
  312. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +685 -0
  313. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +83 -0
  314. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +142 -0
  315. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +141 -0
  316. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +186 -0
  317. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +126 -0
  318. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +168 -0
  319. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +84 -0
  320. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +75 -0
  321. package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +332 -0
  322. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +180 -0
  323. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +137 -0
  324. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +207 -0
  325. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +654 -0
  326. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +28 -0
  327. package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +480 -0
  328. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +780 -0
  329. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +146 -0
  330. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +269 -0
  331. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +100 -0
  332. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +243 -0
  333. package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +125 -0
  334. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +24 -0
  335. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +181 -0
  336. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +83 -0
  337. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +85 -0
  338. package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +123 -0
  339. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +114 -0
  340. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +100 -0
  341. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +291 -0
  342. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +386 -0
  343. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +65 -0
  344. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +594 -0
  345. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +85 -0
  346. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +108 -0
  347. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +398 -0
  348. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +65 -0
  349. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +1275 -0
  350. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +84 -0
  351. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +75 -0
  352. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +65 -0
  353. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +835 -0
  354. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +83 -0
  355. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +71 -0
  356. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +83 -0
  357. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +77 -0
  358. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +92 -0
  359. package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +54 -0
  360. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +171 -0
  361. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +911 -0
  362. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +84 -0
  363. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +75 -0
  364. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +87 -0
  365. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +207 -0
  366. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +84 -0
  367. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +75 -0
  368. package/Sources/RunicCore/Providers/ProviderBranding.swift +25 -0
  369. package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +17 -0
  370. package/Sources/RunicCore/Providers/ProviderDescriptor.swift +129 -0
  371. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +263 -0
  372. package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +98 -0
  373. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +501 -0
  374. package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +68 -0
  375. package/Sources/RunicCore/Providers/Providers.swift +184 -0
  376. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +84 -0
  377. package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +30 -0
  378. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +317 -0
  379. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +84 -0
  380. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +75 -0
  381. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +83 -0
  382. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +75 -0
  383. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +123 -0
  384. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +48 -0
  385. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +144 -0
  386. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +83 -0
  387. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +75 -0
  388. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +72 -0
  389. package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +41 -0
  390. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +724 -0
  391. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +385 -0
  392. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +310 -0
  393. package/Sources/RunicCore/Sync/SyncProtocol.swift +288 -0
  394. package/Sources/RunicCore/Sync/SyncRecord.swift +405 -0
  395. package/Sources/RunicCore/Sync/SyncUsageExample.swift +294 -0
  396. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +775 -0
  397. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +333 -0
  398. package/Sources/RunicCore/Team/TeamModels.swift +583 -0
  399. package/Sources/RunicCore/Team/TeamShowbackExporter.swift +495 -0
  400. package/Sources/RunicCore/Team/TeamStore.swift +792 -0
  401. package/Sources/RunicCore/TextParsing.swift +55 -0
  402. package/Sources/RunicCore/UsageFetcher.swift +694 -0
  403. package/Sources/RunicCore/UsageFormatter.swift +410 -0
  404. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +329 -0
  405. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +448 -0
  406. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +550 -0
  407. package/Sources/RunicCore/UsageLedger/UsageLedger.swift +29 -0
  408. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +660 -0
  409. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +209 -0
  410. package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +417 -0
  411. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +245 -0
  412. package/Sources/RunicCore/UsagePace.swift +81 -0
  413. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +73 -0
  414. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +74 -0
  415. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +208 -0
  416. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +455 -0
  417. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +132 -0
  418. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +589 -0
  419. package/Sources/RunicCore/WidgetSnapshot.swift +183 -0
  420. package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +14 -0
  421. package/Sources/RunicMacros/ProviderRegistrationMacros.swift +216 -0
  422. package/Sources/RunicWidget/RunicWidgetBundle.swift +79 -0
  423. package/Sources/RunicWidget/RunicWidgetProvider.swift +283 -0
  424. package/Sources/RunicWidget/RunicWidgetViews.swift +680 -0
  425. package/Tests/RunicTests/AntigravityStatusProbeTests.swift +50 -0
  426. package/Tests/RunicTests/AppDelegateTests.swift +43 -0
  427. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +22 -0
  428. package/Tests/RunicTests/CLIArgumentParsingTests.swift +70 -0
  429. package/Tests/RunicTests/CLICostTests.swift +65 -0
  430. package/Tests/RunicTests/CLIProviderSelectionTests.swift +22 -0
  431. package/Tests/RunicTests/CLISnapshotTests.swift +72 -0
  432. package/Tests/RunicTests/CLIWebFallbackTests.swift +58 -0
  433. package/Tests/RunicTests/ClaudeOAuthTests.swift +164 -0
  434. package/Tests/RunicTests/ClaudeResilienceTests.swift +30 -0
  435. package/Tests/RunicTests/ClaudeUsageTests.swift +370 -0
  436. package/Tests/RunicTests/CodexOAuthTests.swift +122 -0
  437. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +82 -0
  438. package/Tests/RunicTests/CopilotTokenResolverTests.swift +75 -0
  439. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +75 -0
  440. package/Tests/RunicTests/CostUsageDecodingTests.swift +394 -0
  441. package/Tests/RunicTests/CostUsagePricingTests.swift +50 -0
  442. package/Tests/RunicTests/CostUsageScannerTests.swift +700 -0
  443. package/Tests/RunicTests/CursorStatusProbeTests.swift +323 -0
  444. package/Tests/RunicTests/FactoryStatusProbeTests.swift +69 -0
  445. package/Tests/RunicTests/GeminiAPITestHelpers.swift +94 -0
  446. package/Tests/RunicTests/GeminiLoginAlertTests.swift +28 -0
  447. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +283 -0
  448. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +279 -0
  449. package/Tests/RunicTests/GeminiStatusProbeTests.swift +233 -0
  450. package/Tests/RunicTests/GeminiTestEnvironment.swift +87 -0
  451. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +73 -0
  452. package/Tests/RunicTests/InstallOriginTests.swift +18 -0
  453. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +9 -0
  454. package/Tests/RunicTests/LiveAccountTests.swift +24 -0
  455. package/Tests/RunicTests/LoadingPatternTests.swift +35 -0
  456. package/Tests/RunicTests/MenuCardModelTests.swift +545 -0
  457. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +8 -0
  458. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +8 -0
  459. package/Tests/RunicTests/MiniMaxParsingTests.swift +68 -0
  460. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +8 -0
  461. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +112 -0
  462. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +17 -0
  463. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +75 -0
  464. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +27 -0
  465. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +134 -0
  466. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +53 -0
  467. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +92 -0
  468. package/Tests/RunicTests/PathBuilderTests.swift +237 -0
  469. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +160 -0
  470. package/Tests/RunicTests/ProviderIconResourcesTests.swift +38 -0
  471. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +16 -0
  472. package/Tests/RunicTests/ProviderRegistryTests.swift +29 -0
  473. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +202 -0
  474. package/Tests/RunicTests/ProviderToggleStoreTests.swift +51 -0
  475. package/Tests/RunicTests/RunicTests.swift +120 -0
  476. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +36 -0
  477. package/Tests/RunicTests/SettingsStoreTests.swift +161 -0
  478. package/Tests/RunicTests/StatusItemAnimationTests.swift +49 -0
  479. package/Tests/RunicTests/StatusMenuTests.swift +434 -0
  480. package/Tests/RunicTests/StatusProbeTests.swift +377 -0
  481. package/Tests/RunicTests/SubprocessRunnerTests.swift +20 -0
  482. package/Tests/RunicTests/SubscriptionDetectionTests.swift +65 -0
  483. package/Tests/RunicTests/TTYCommandRunnerTests.swift +148 -0
  484. package/Tests/RunicTests/TTYIntegrationTests.swift +41 -0
  485. package/Tests/RunicTests/TeamShowbackExporterTests.swift +159 -0
  486. package/Tests/RunicTests/TextParsingTests.swift +12 -0
  487. package/Tests/RunicTests/UsageFormatterTests.swift +112 -0
  488. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +172 -0
  489. package/Tests/RunicTests/UsageLedgerForecastTests.swift +213 -0
  490. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +83 -0
  491. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +170 -0
  492. package/Tests/RunicTests/UsagePaceTests.swift +77 -0
  493. package/Tests/RunicTests/UsagePaceTextTests.swift +109 -0
  494. package/Tests/RunicTests/UsagePolicyEngineTests.swift +91 -0
  495. package/Tests/RunicTests/WidgetSnapshotTests.swift +45 -0
  496. package/Tests/RunicTests/ZaiProviderTests.swift +61 -0
  497. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +6 -0
  498. package/TestsLinux/PlatformGatingTests.swift +46 -0
  499. package/appcast.xml +105 -0
  500. package/assets/logo-concept-stack.svg +24 -0
  501. package/assets/logo-minimal-bars.svg +15 -0
  502. package/assets/logo.svg +20 -0
  503. package/assets/logo2.svg +43 -0
  504. package/bin/docs-list +5 -0
  505. package/bin/install-runic-cli.sh +32 -0
  506. package/build-and-run.sh +369 -0
  507. package/docs/.nojekyll +1 -0
  508. package/docs/CNAME +1 -0
  509. package/docs/antigravity.md +75 -0
  510. package/docs/architecture.md +32 -0
  511. package/docs/claude.md +93 -0
  512. package/docs/cli-commands/integration_guide.md +371 -0
  513. package/docs/cli-commands/quick_reference.md +362 -0
  514. package/docs/cli-commands/readme.md +361 -0
  515. package/docs/cli.md +156 -0
  516. package/docs/cloudkit_sync_guide.md +495 -0
  517. package/docs/codebase_review.md +288 -0
  518. package/docs/codex-oauth.md +635 -0
  519. package/docs/codex.md +104 -0
  520. package/docs/codexbar.png +0 -0
  521. package/docs/copilot.md +44 -0
  522. package/docs/cursor.md +46 -0
  523. package/docs/design/designcomparisonchart.md +510 -0
  524. package/docs/design/implementationguide.md +792 -0
  525. package/docs/design/menubaricondesignconcepts.md +796 -0
  526. package/docs/design/mockups/hybridiconconcept.svg +62 -0
  527. package/docs/design/mockups/infinityiconconcept.svg +41 -0
  528. package/docs/design/mockups/infinityiconstates.svg +129 -0
  529. package/docs/design/mockups/wavelogorefined.svg +48 -0
  530. package/docs/design/quick_summary.md +426 -0
  531. package/docs/design/readme.md +478 -0
  532. package/docs/design/sizespecifications.md +575 -0
  533. package/docs/design-system.md +836 -0
  534. package/docs/factory.md +103 -0
  535. package/docs/gemini.md +73 -0
  536. package/docs/icon.md +37 -0
  537. package/docs/icon.png +0 -0
  538. package/docs/index.html +175 -0
  539. package/docs/keychain_fix.md +274 -0
  540. package/docs/minimax.md +71 -0
  541. package/docs/packaging.md +25 -0
  542. package/docs/progress-bar-improvements.md +240 -0
  543. package/docs/progress-bar-quick-reference.md +283 -0
  544. package/docs/progress-bar-technical-details.md +432 -0
  545. package/docs/progress-bar-visual-test.swift +375 -0
  546. package/docs/provider.md +202 -0
  547. package/docs/providers.md +86 -0
  548. package/docs/refactor/macros.md +35 -0
  549. package/docs/refresh-loop.md +22 -0
  550. package/docs/releasing-homebrew.md +35 -0
  551. package/docs/releasing.md +121 -0
  552. package/docs/research/concrete_todo_2026-02-22.md +47 -0
  553. package/docs/research/core-feature-research.md +248 -0
  554. package/docs/research/feature_uiux_roadmap_2026-02-22.md +202 -0
  555. package/docs/research/parity_matrix.md +49 -0
  556. package/docs/research/provider_expansion_candidates_2026-02-22.md +80 -0
  557. package/docs/research/provider_model_token_project_matrix_2026-02-23.md +46 -0
  558. package/docs/research/release_gate_checklist.md +57 -0
  559. package/docs/site.css +505 -0
  560. package/docs/sparkle.md +28 -0
  561. package/docs/status.md +28 -0
  562. package/docs/sync/implementation.md +393 -0
  563. package/docs/sync/readme.md +317 -0
  564. package/docs/sync_files_summary.md +391 -0
  565. package/docs/sync_quick_reference.md +225 -0
  566. package/docs/todo.md +11 -0
  567. package/docs/ui.md +29 -0
  568. package/docs/web-integration.md +9 -0
  569. package/docs/widgets.md +85 -0
  570. package/docs/workflows/error-handling-improvements.md +434 -0
  571. package/docs/workflows/folder_structure.md +299 -0
  572. package/docs/workflows/loading_states.md +446 -0
  573. package/docs/workflows/loading_states_examples.md +565 -0
  574. package/docs/workflows/loading_states_flow.md +430 -0
  575. package/docs/workflows/performance_optimizations.md +457 -0
  576. package/docs/workflows/phase1-cli-testing-report.md +313 -0
  577. package/docs/workflows/quick_reference.md +228 -0
  578. package/docs/workflows/readme.md +59 -0
  579. package/docs/workflows/recent_improvements.md +275 -0
  580. package/docs/workflows/sync_enablement_summary.md +400 -0
  581. package/docs/zai.md +43 -0
  582. package/package.json +36 -0
  583. package/runic.png +0 -0
  584. package/version.env +2 -0
@@ -0,0 +1,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol OpenRouterTokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum OpenRouterTokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainOpenRouterTokenStore: OpenRouterTokenStoring {
25
+ private static let log = RunicLog.logger("openrouter-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "openrouter-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating OpenRouter token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw OpenRouterTokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw OpenRouterTokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw OpenRouterTokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw OpenRouterTokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }
@@ -0,0 +1,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol PerplexityTokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum PerplexityTokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainPerplexityTokenStore: PerplexityTokenStoring {
25
+ private static let log = RunicLog.logger("perplexity-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "perplexity-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating Perplexity token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw PerplexityTokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw PerplexityTokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw PerplexityTokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw PerplexityTokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }
@@ -0,0 +1,30 @@
1
+ import RunicCore
2
+ import Foundation
3
+
4
+ struct ProviderToggleStore {
5
+ private let userDefaults: UserDefaults
6
+ private let key = "providerToggles"
7
+
8
+ init(userDefaults: UserDefaults = .standard) {
9
+ self.userDefaults = userDefaults
10
+ }
11
+
12
+ func isEnabled(metadata: ProviderMetadata) -> Bool {
13
+ self.load()[metadata.cliName] ?? metadata.defaultEnabled
14
+ }
15
+
16
+ func setEnabled(_ enabled: Bool, metadata: ProviderMetadata) {
17
+ var toggles = self.load()
18
+ toggles[metadata.cliName] = enabled
19
+ self.userDefaults.set(toggles, forKey: self.key)
20
+ }
21
+
22
+ private func load() -> [String: Bool] {
23
+ (self.userDefaults.dictionary(forKey: self.key) as? [String: Bool]) ?? [:]
24
+ }
25
+
26
+ func purgeLegacyKeys() {
27
+ self.userDefaults.removeObject(forKey: "showCodexUsage")
28
+ self.userDefaults.removeObject(forKey: "showClaudeUsage")
29
+ }
30
+ }
@@ -0,0 +1,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol QwenTokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum QwenTokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainQwenTokenStore: QwenTokenStoring {
25
+ private static let log = RunicLog.logger("qwen-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "qwen-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating Qwen token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw QwenTokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw QwenTokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw QwenTokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw QwenTokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }
@@ -0,0 +1,143 @@
1
+ import RunicCore
2
+ import Foundation
3
+ import Security
4
+
5
+ protocol SambaNovaTokenStoring: Sendable {
6
+ func loadToken() throws -> String?
7
+ func storeToken(_ token: String?) throws
8
+ }
9
+
10
+ enum SambaNovaTokenStoreError: LocalizedError {
11
+ case keychainStatus(OSStatus)
12
+ case invalidData
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case let .keychainStatus(status):
17
+ "Keychain error: \(status)"
18
+ case .invalidData:
19
+ "Keychain returned invalid data."
20
+ }
21
+ }
22
+ }
23
+
24
+ struct KeychainSambaNovaTokenStore: SambaNovaTokenStoring {
25
+ private static let log = RunicLog.logger("sambanova-token-store")
26
+
27
+ private let service = "com.sriinnu.athena.Runic"
28
+ private let account = "sambanova-api-token"
29
+
30
+ func loadToken() throws -> String? {
31
+ // Try standard keychain first.
32
+ if let token = try self.readToken(dataProtection: false) {
33
+ return token
34
+ }
35
+ // Migrate from the old Data Protection keychain if present.
36
+ if let token = try self.readToken(dataProtection: true) {
37
+ Self.log.info("Migrating SambaNova token from Data Protection keychain")
38
+ // Store first, delete old only on success — prevents data loss.
39
+ if (try? self.storeToken(token)) != nil {
40
+ try? self.deleteToken(dataProtection: true)
41
+ }
42
+ return token
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func storeToken(_ token: String?) throws {
48
+ let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
49
+ if cleaned == nil || cleaned?.isEmpty == true {
50
+ try? self.deleteToken(dataProtection: false)
51
+ try? self.deleteToken(dataProtection: true)
52
+ return
53
+ }
54
+
55
+ let data = cleaned!.data(using: .utf8)!
56
+ let query = self.baseQuery(dataProtection: false)
57
+ let attributes: [String: Any] = [
58
+ kSecValueData as String: data,
59
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
60
+ ]
61
+
62
+ // Try update first.
63
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
64
+ if updateStatus == errSecSuccess { return }
65
+
66
+ if updateStatus != errSecItemNotFound {
67
+ // Item exists but update failed — delete and re-add to reset ACL.
68
+ try? self.deleteToken(dataProtection: false)
69
+ }
70
+
71
+ var addQuery = query
72
+ for (key, value) in attributes { addQuery[key] = value }
73
+
74
+ // Grant the calling app permanent access so macOS never shows a
75
+ // Keychain password dialog for this item.
76
+ if let access = Self.createSelfAccess() {
77
+ addQuery[kSecAttrAccess as String] = access
78
+ }
79
+
80
+ let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
81
+ guard addStatus == errSecSuccess else {
82
+ Self.log.error("Keychain add failed: \(addStatus)")
83
+ throw SambaNovaTokenStoreError.keychainStatus(addStatus)
84
+ }
85
+ }
86
+
87
+ // MARK: - Private
88
+
89
+ private func readToken(dataProtection: Bool) throws -> String? {
90
+ var result: CFTypeRef?
91
+ var query = self.baseQuery(dataProtection: dataProtection)
92
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
93
+ query[kSecReturnData as String] = true
94
+
95
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
96
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
97
+ return nil
98
+ }
99
+ guard status == errSecSuccess else {
100
+ Self.log.error("Keychain read failed: \(status)")
101
+ throw SambaNovaTokenStoreError.keychainStatus(status)
102
+ }
103
+ guard let data = result as? Data else {
104
+ throw SambaNovaTokenStoreError.invalidData
105
+ }
106
+ let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
107
+ return (token?.isEmpty ?? true) ? nil : token
108
+ }
109
+
110
+ private func deleteToken(dataProtection: Bool) throws {
111
+ let query = self.baseQuery(dataProtection: dataProtection)
112
+ let status = SecItemDelete(query as CFDictionary)
113
+ if status == errSecSuccess || status == errSecItemNotFound { return }
114
+ Self.log.error("Keychain delete failed: \(status)")
115
+ throw SambaNovaTokenStoreError.keychainStatus(status)
116
+ }
117
+
118
+ private func baseQuery(dataProtection: Bool) -> [String: Any] {
119
+ var q: [String: Any] = [
120
+ kSecClass as String: kSecClassGenericPassword,
121
+ kSecAttrService as String: self.service,
122
+ kSecAttrAccount as String: self.account,
123
+ ]
124
+ if dataProtection {
125
+ q[kSecUseDataProtectionKeychain as String] = true
126
+ }
127
+ return q
128
+ }
129
+
130
+ /// Creates a SecAccess that grants the calling application permanent
131
+ /// read/write access without triggering the macOS Keychain password dialog.
132
+ private static func createSelfAccess() -> SecAccess? {
133
+ var trustedSelf: SecTrustedApplication?
134
+ guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
135
+ let trustedSelf else { return nil }
136
+ var access: SecAccess?
137
+ guard SecAccessCreate(
138
+ "Runic API Token" as CFString,
139
+ [trustedSelf] as CFArray,
140
+ &access) == errSecSuccess else { return nil }
141
+ return access
142
+ }
143
+ }