eggpool 0.3.4__tar.gz → 0.3.5__tar.gz

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 (240) hide show
  1. eggpool-0.3.5/AGENTS.md +103 -0
  2. {eggpool-0.3.4 → eggpool-0.3.5}/CHANGELOG.md +6 -0
  3. eggpool-0.3.5/PKG-INFO +175 -0
  4. eggpool-0.3.5/README.md +131 -0
  5. {eggpool-0.3.4 → eggpool-0.3.5}/architecture/README.md +12 -6
  6. {eggpool-0.3.4 → eggpool-0.3.5}/pyproject.toml +1 -1
  7. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/accounts/state.py +1 -1
  8. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/app.py +18 -19
  9. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/cli_full.py +132 -64
  10. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/render.py +18 -7
  11. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/static/dashboard.css +13 -1
  12. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/static/dashboard.js +16 -1
  13. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/rollup_repository.py +9 -3
  14. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/health/backoff.py +1 -1
  15. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/health/circuit_breaker.py +1 -1
  16. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/health/health_manager.py +2 -2
  17. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/lifecycle/uninstall.py +39 -6
  18. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/metrics/buffer.py +5 -5
  19. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/proxy/cost_reporting.py +1 -1
  20. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/proxy/sse_observer.py +8 -5
  21. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/proxy/usage.py +1 -1
  22. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/quota/estimation.py +6 -6
  23. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/quota/scorer.py +2 -2
  24. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/attempt_finalizer.py +2 -2
  25. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/coordinator.py +3 -3
  26. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/finalizer.py +1 -1
  27. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/routing/router.py +3 -3
  28. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/runtime_dispatch.py +1 -1
  29. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/runtime_metrics.py +19 -13
  30. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/update_checker.py +66 -39
  31. {eggpool-0.3.4 → eggpool-0.3.5}/uv.lock +1 -1
  32. eggpool-0.3.4/AGENTS.md +0 -243
  33. eggpool-0.3.4/PKG-INFO +0 -915
  34. eggpool-0.3.4/README.md +0 -871
  35. {eggpool-0.3.4 → eggpool-0.3.5}/.env.example +0 -0
  36. {eggpool-0.3.4 → eggpool-0.3.5}/.github/workflows/ci.yml +0 -0
  37. {eggpool-0.3.4 → eggpool-0.3.5}/.github/workflows/release.yml +0 -0
  38. {eggpool-0.3.4 → eggpool-0.3.5}/.gitignore +0 -0
  39. {eggpool-0.3.4 → eggpool-0.3.5}/LICENSE +0 -0
  40. {eggpool-0.3.4 → eggpool-0.3.5}/config-examples/claude-code.env +0 -0
  41. {eggpool-0.3.4 → eggpool-0.3.5}/config-examples/opencode.jsonc +0 -0
  42. {eggpool-0.3.4 → eggpool-0.3.5}/config.example.toml +0 -0
  43. {eggpool-0.3.4 → eggpool-0.3.5}/deploy/eggpool-logrotate.conf +0 -0
  44. {eggpool-0.3.4 → eggpool-0.3.5}/deploy/eggpool.service +0 -0
  45. {eggpool-0.3.4 → eggpool-0.3.5}/deploy/env.example +0 -0
  46. {eggpool-0.3.4 → eggpool-0.3.5}/docs/backup-restore.md +0 -0
  47. {eggpool-0.3.4 → eggpool-0.3.5}/docs/deployment.md +0 -0
  48. {eggpool-0.3.4 → eggpool-0.3.5}/docs/filesystem-layout.md +0 -0
  49. {eggpool-0.3.4 → eggpool-0.3.5}/docs/firewall.md +0 -0
  50. {eggpool-0.3.4 → eggpool-0.3.5}/docs/model-limits.md +0 -0
  51. {eggpool-0.3.4 → eggpool-0.3.5}/docs/network-diagnostics.md +0 -0
  52. {eggpool-0.3.4 → eggpool-0.3.5}/docs/providers.md +0 -0
  53. {eggpool-0.3.4 → eggpool-0.3.5}/docs/proxy.md +0 -0
  54. {eggpool-0.3.4 → eggpool-0.3.5}/docs/raspberry-pi.md +0 -0
  55. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/__init__.py +0 -0
  56. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/check_database.py +0 -0
  57. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/install.sh +0 -0
  58. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/install_prompt.py +0 -0
  59. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/smoke_test.py +0 -0
  60. {eggpool-0.3.4 → eggpool-0.3.5}/scripts/verify_upstream_auth.py +0 -0
  61. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/__init__.py +0 -0
  62. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/__main__.py +0 -0
  63. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/_share/.env.example +0 -0
  64. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/_share/config.example.toml +0 -0
  65. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/accounts/__init__.py +0 -0
  66. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/accounts/registry.py +0 -0
  67. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/__init__.py +0 -0
  68. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/backoff.py +0 -0
  69. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/chat_completions.py +0 -0
  70. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/errors.py +0 -0
  71. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/messages.py +0 -0
  72. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/models.py +0 -0
  73. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/network.py +0 -0
  74. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/proxy_request.py +0 -0
  75. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/runtime.py +0 -0
  76. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/stats.py +0 -0
  77. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/api/update.py +0 -0
  78. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/auth.py +0 -0
  79. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/background/__init__.py +0 -0
  80. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/background/backup.py +0 -0
  81. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/background/cleanup.py +0 -0
  82. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/__init__.py +0 -0
  83. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/cache.py +0 -0
  84. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/catalog_resolvers.py +0 -0
  85. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/fetcher.py +0 -0
  86. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/limits.py +0 -0
  87. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/normalizer.py +0 -0
  88. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/pricing.py +0 -0
  89. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/pricing_aliases.py +0 -0
  90. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/pricing_resolver.py +0 -0
  91. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/protocols.py +0 -0
  92. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/catalog/service.py +0 -0
  93. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/cli.py +0 -0
  94. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/config.py +0 -0
  95. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/constants.py +0 -0
  96. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/cost_recompute.py +0 -0
  97. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/__init__.py +0 -0
  98. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/_resources.py +0 -0
  99. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/escape.py +0 -0
  100. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/routes.py +0 -0
  101. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
  102. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/static/favicon.svg +0 -0
  103. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/theme.py +0 -0
  104. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
  105. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
  106. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
  107. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
  108. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
  109. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
  110. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
  111. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
  112. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Discord.toml +0 -0
  113. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
  114. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
  115. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
  116. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
  117. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
  118. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
  119. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
  120. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
  121. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
  122. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
  123. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
  124. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Nord.toml +0 -0
  125. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
  126. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
  127. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
  128. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
  129. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
  130. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
  131. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
  132. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
  133. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
  134. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
  135. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
  136. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/acton.toml +0 -0
  137. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/bam.toml +0 -0
  138. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
  139. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/berlin.toml +0 -0
  140. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
  141. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/broc.toml +0 -0
  142. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/cork.toml +0 -0
  143. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/ferra.toml +0 -0
  144. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/forest.toml +0 -0
  145. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
  146. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/midnight.toml +0 -0
  147. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/oslo.toml +0 -0
  148. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/plum.toml +0 -0
  149. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/portland.toml +0 -0
  150. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/sunset.toml +0 -0
  151. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/tofino.toml +0 -0
  152. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
  153. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/dashboard/themes/vik.toml +0 -0
  154. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/__init__.py +0 -0
  155. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/connection.py +0 -0
  156. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/migrations.py +0 -0
  157. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/repositories.py +0 -0
  158. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0001_initial.sql +0 -0
  159. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0002_indexes.sql +0 -0
  160. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
  161. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
  162. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
  163. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
  164. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
  165. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
  166. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
  167. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
  168. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
  169. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
  170. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
  171. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
  172. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
  173. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
  174. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
  175. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
  176. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
  177. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
  178. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
  179. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
  180. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
  181. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
  182. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
  183. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
  184. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
  185. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
  186. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
  187. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
  188. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
  189. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0032_usage_rollups.sql +0 -0
  190. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/0033_request_provider_local_cost.sql +0 -0
  191. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/db/schema/checksums.json +0 -0
  192. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/deploy/__init__.py +0 -0
  193. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/deploy_user.py +0 -0
  194. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/errors.py +0 -0
  195. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/fastcli.py +0 -0
  196. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/health/__init__.py +0 -0
  197. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/integrations/__init__.py +0 -0
  198. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/integrations/opencode.py +0 -0
  199. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/lifecycle/__init__.py +0 -0
  200. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/lifecycle/backup.py +0 -0
  201. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/logging.py +0 -0
  202. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/metrics/__init__.py +0 -0
  203. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/models/__init__.py +0 -0
  204. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/models/api.py +0 -0
  205. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/models/config.py +0 -0
  206. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/models/database.py +0 -0
  207. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/models/domain.py +0 -0
  208. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/onboard.py +0 -0
  209. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/__init__.py +0 -0
  210. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/_templates.toml +0 -0
  211. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/auth.py +0 -0
  212. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/client_pool.py +0 -0
  213. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/connect.py +0 -0
  214. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/contract.py +0 -0
  215. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/dns_cache.py +0 -0
  216. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/outbound.py +0 -0
  217. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/providers/pproxy_transport.py +0 -0
  218. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/proxy/__init__.py +0 -0
  219. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/proxy/client.py +0 -0
  220. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/py.typed +0 -0
  221. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/quota/__init__.py +0 -0
  222. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/quota/audit.py +0 -0
  223. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/quota/reservation.py +0 -0
  224. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/__init__.py +0 -0
  225. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/body.py +0 -0
  226. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/request/limits.py +0 -0
  227. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/retry/__init__.py +0 -0
  228. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/retry/classification.py +0 -0
  229. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/routing/__init__.py +0 -0
  230. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/routing/eligibility.py +0 -0
  231. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/routing/provider.py +0 -0
  232. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/runtime.py +0 -0
  233. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/runtime_paths.py +0 -0
  234. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/security/__init__.py +0 -0
  235. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/security/redaction.py +0 -0
  236. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/stats/__init__.py +0 -0
  237. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/stats/grouped_timeseries.py +0 -0
  238. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/stats/queries.py +0 -0
  239. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/stats/service.py +0 -0
  240. {eggpool-0.3.4 → eggpool-0.3.5}/src/eggpool/toml_edit.py +0 -0
@@ -0,0 +1,103 @@
1
+ # AGENTS.md
2
+
3
+ ## Skills
4
+
5
+ Project-specific skills are in `.opencode/skills/`:
6
+
7
+ - `architecture` — design principles, request lifecycle, invariants, error hierarchy
8
+ - `deployment` — production deployment, systemd, operational scripts
9
+ - `development` — linting, testing, pre-commit checks, code style
10
+
11
+ ## Quick Start
12
+
13
+ - Package manager: **uv** (not pip). Install deps: `uv sync --extra dev`
14
+ - Entry point: `src/eggpool/cli.py` → `eggpool` console script
15
+ - Config: `config.toml` + `.env` for API keys
16
+
17
+ ## Pre-commit Checks (run before every commit)
18
+
19
+ ```bash
20
+ uv run ruff format --check src/ tests/ scripts/
21
+ uv run ruff check src/ tests/ scripts/
22
+ uv run pyright src/ scripts/
23
+ uv run pytest
24
+ ```
25
+
26
+ All four must pass with zero errors.
27
+
28
+ ## Code Style
29
+
30
+ - Python 3.11+ with `from __future__ import annotations` in ALL files
31
+ - Type hints on all function signatures and return types
32
+ - Ruff: E, F, W, I, N, UP, B, A, SIM, TCH rules
33
+ - Pyright strict mode — covers `src/` AND `scripts/` (not tests)
34
+ - Line length: 88 chars
35
+ - Use `NoReturn` for functions that never return (e.g., `sys.exit`)
36
+
37
+ ## Testing
38
+
39
+ - pytest with `asyncio_mode = "strict"` (from `pyproject.toml`)
40
+ - respx for HTTPX upstream mocking
41
+ - Tests in `tests/unit/`, `tests/integration/`, `tests/contract/`
42
+ - Provider contract tests: `uv run pytest tests/unit/test_contract.py tests/unit/test_contract_urls.py -v`
43
+
44
+ ## File Organization
45
+
46
+ - Source: `src/eggpool/`
47
+ - Tests: `tests/` (mirrors src structure)
48
+ - Config: `config.example.toml`, `.env.example`
49
+ - DB schema: `src/eggpool/db/schema/`
50
+ - Scripts: `scripts/` (operational, also type-checked by pyright)
51
+ - Deployment: `deploy/`
52
+
53
+ ## Architecture Index
54
+
55
+ > Full design details are in `architecture/README.md` and the `architecture` skill.
56
+
57
+ - **Request lifecycle**: `RequestCoordinator` orchestrates endpoint → routing → persistence → dispatch → finalization. See `architecture/README.md` § Request Lifecycle.
58
+ - **Multi-provider architecture**: provider-suffixed model IDs (`model-id/provider-id`), `ProviderClientPool`, `OutboundClientManager`. See `architecture/README.md` § Multi-Provider Architecture.
59
+ - **Provider contracts**: `compose_provider_url()` is the single source of truth for upstream URLs. See `architecture/README.md` § Provider Contracts and § Provider Contract Rendering.
60
+ - **Database invariants**: SQLite WAL, single-connection serialization, `async with db.transaction():` for all DML. See `architecture/README.md` § Database.
61
+ - **Quota and routing**: tier-based routing via `routing_priority`, `QuotaFairScorer`, upstream-authoritative suppression. See `architecture/README.md` § Quota and Routing.
62
+ - **Error hierarchy**: `AggregatorError` → `UpstreamError` → specific subclasses. See `architecture/README.md` § Error Hierarchy.
63
+ - **Process model**: supervisor + Granian worker, PID file lifecycle, daemon mode. See `architecture/README.md` § Daemon Mode.
64
+ - **Dashboard**: server-rendered HTML, 12 pages, Chart.js v4, grouped timeseries, CSS tooltips. See `architecture` skill § Dashboard.
65
+ - **Observability**: attempt analytics, routing analytics, latency phases, pending health, runtime metrics. See `architecture` skill § Runtime Observability.
66
+
67
+ ## Gotchas
68
+
69
+ - Configuration changes require a service restart; live reload is intentionally not supported
70
+ - No pre-commit hooks are configured in this repo; CI runs ruff, pyright, and pytest via GitHub Actions
71
+ - **`static_models` is the source of truth for provider-specific protocol** — `FAMILY_PROTOCOLS` (`src/eggpool/catalog/protocols.py`) is a global fallback that applies when no explicit override exists. Providers like `minimax-cn` that serve MiniMax models on the OpenAI-compatible surface **must** ship `[[providers.<id>.static_models]]` rows with `protocol = "openai"`, otherwise the live `/v1/models` fetch resolves `MiniMax-M*` via the `minimax-` family prefix to `anthropic` and the provider protocol constraint check (`src/eggpool/catalog/service.py:533-545`) clears the protocol to `None`, producing `ModelUnavailableError("Model 'MiniMax-M3' has unresolved protocol")` instead of the more obvious 400 `ProtocolMismatchError`. Static seeds with `protocol_source = "static_config"` survive the live merge via `ModelCatalogCache._preserve_static_fields` (`src/eggpool/catalog/cache.py:146-187`). The same model ID may also be flipped on a per-provider basis via `[providers.<id>.model_overrides."<model-id>"].protocol = "openai"` for operator overrides that go beyond the bundled template.
72
+ - **Upstream-authoritative suppression**: local quota estimates are advisory by default (`local_quota_mode = "score_only"`). Above-capacity accounts stay eligible; only upstream-observed failures (429/402/5xx/auth) and explicit operator disablement suppress routing. Switch to `hard_cap` only as an opt-in escape hatch.
73
+ - **Backoff persistence**: upstream-derived backoffs survive restarts via the `account_backoffs` table (`src/eggpool/db/schema/0024_account_backoffs.sql`). Hydration runs at startup after account sync, best-effort (never blocks boot). Local cost overruns must never be persisted as backoff rows.
74
+ - **Synthetic 503 vs 502**: `ModelUnavailableError` (503) is reserved for genuine pre-dispatch unavailability. `UpstreamExhaustedError` (502) is raised when every candidate account was attempted and exhausted mid-request. Single-account upstream errors pass through to the client rather than becoming synthetic 503s.
75
+ - **Streaming finalizer shielding**: streaming `_build_stream_generator` finalization runs under `asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task cancellation cannot kill the finalizer while it holds the DB lock. Leaks that escape this path are caught by the periodic `stale_request_finalizer` background task (`app._finalize_stale_requests`, runs every 60s) which force-finalizes any request that has been `pending` longer than `upstream.read_timeout_s`.
76
+ - **Startup crash recovery**: `_crash_recovery` runs at every startup and recovers ALL pending requests and ALL active reservations with no time threshold. A process restart is a definitive boundary, so leaked state from the previous process is unconditionally cleaned up. If `Crash recovery: marked N stale requests` appears in logs, the safety net caught leaks from the previous run.
77
+ - **Pricing pipeline**: prices flow TOML override → upstream metadata → external catalog (OpenRouter / OpenCode Zen via the alias registry). Cost precedence: `provider_reported > derived/partial/exact > estimated (reservation fallback) > unknown (zero)`. See the `architecture` skill for the full pricing resolution details.
78
+ - **`eggpool stats recompute-costs [--dry-run|--apply] [--limit N]`**: walks the requests table in started_at DESC order, recomputes cost from the current price snapshots, and reports / applies the change. Default is `--dry-run`. Use after upgrading the resolver to fix inflated totals on cached-token-heavy models (e.g. MiMo 2.5). Implemented in `src/eggpool/cost_recompute.py` and reuses the live `CostCalculator` so the new values match what the finalizer would write today.
79
+ - **Automatic backups**: in-process daily backups run by default under the `automatic_backup` supervised task (`src/eggpool/background/backup.py`). Uses stdlib `sqlite3.Connection.backup()` for consistent snapshots, atomic archive publication (write-to-temp + rename), and count-based retention (default 14). Controlled by `[backup]` config section. The `eggpool deploy backup-cron` path remains available for operators who prefer external scheduling.
80
+ - **DNS cache**: `OutboundClientManager` and `ProviderClientPool` both integrate a `DnsNetworkBackend` that caches resolved DNS entries in memory. The cache reduces connection latency for repeated requests to the same upstream hosts. Controlled by `[network.dns_cache]` config. When a proxy is configured for an account, that account's client uses the proxy transport instead of the cached backend.
81
+
82
+ ## Error Handling
83
+
84
+ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or `raise ... from None`.
85
+
86
+ - `AggregatorError` → `ConfigError`, `DatabaseError`, `ProxyError`
87
+ - `UpstreamError` (has `status_code`) → `TemporaryUpstreamError`, `TransientUpstreamError`, `AuthenticationError`, `QuotaExhaustedError`, `RateLimitError` (has `retry_after`), `ModelUnavailableError`
88
+ - `ModelNotFoundError` (has `model_id`), `NoEligibleAccountError`, `CatalogUnavailableError`, `AuthenticationUnavailableError`, `UpstreamExhaustedError`, `AccountSuspendedError`, `RequestTooLargeError`, `ContextLimitExceededError`
89
+
90
+ ## Fast-Path CLI
91
+
92
+ - `src/eggpool/cli.py` is a tiny bootstrap (74 lines)
93
+ - `main()` calls `eggpool.fastcli.maybe_run_fast_command()` first; recognized fast commands (`croncheck`, `ensure-running`) are dispatched without importing Click
94
+ - **Do not add transitive imports to `runtime_paths` or `fastcli`** — they are stdlib-only and must stay lightweight for the Raspberry Pi watchdog contract
95
+ - Unrecognized commands fall through to `eggpool.cli_full`, which holds the heavy Click CLI
96
+ - Public symbols (`cli`, helpers used by tests) are lazily forwarded from `cli_full` via PEP 562 `__getattr__` — so `from eggpool.cli import cli` and existing test imports still work without loading the full graph
97
+ - See `plans/lightweight-cli-watchdog.md` for the full design
98
+
99
+ ## Git Workflow
100
+
101
+ - Branch: `main`
102
+ - Commit messages: concise, imperative mood
103
+ - Never commit secrets, API keys, or `.env` files
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.5] - 2026-06-27
11
+
12
+ ### Changed
13
+
14
+ - **README rewrite**: condensed the README from 870 lines to a concise, user-focused overview with quick start, CLI reference, configuration summary, API endpoints, and a documentation table linking to dedicated docs. Detailed content (deployment, providers, proxy, backup, model limits, Raspberry Pi, firewall, filesystem layout, network diagnostics) now lives in `docs/` and is linked from the README where it makes sense.
15
+
10
16
  ## [0.3.4] - 2026-06-27
11
17
 
12
18
  ### Fixed
eggpool-0.3.5/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: eggpool
3
+ Version: 0.3.5
4
+ Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
5
+ Project-URL: Homepage, https://github.com/eggstack/eggpool
6
+ Project-URL: Repository, https://github.com/eggstack/eggpool
7
+ Project-URL: Issues, https://github.com/eggstack/eggpool/issues
8
+ Project-URL: Documentation, https://github.com/eggstack/eggpool/tree/main/docs
9
+ Project-URL: Changelog, https://github.com/eggstack/eggpool/blob/main/CHANGELOG.md
10
+ Author-email: David Bowman <dbowman91@proton.me>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: aggregation,anthropic,llm,multi-account,openai,opencode,proxy,router
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Framework :: FastAPI
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: System :: Monitoring
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: aiosqlite
28
+ Requires-Dist: click
29
+ Requires-Dist: fastapi
30
+ Requires-Dist: granian[pname]
31
+ Requires-Dist: httpx
32
+ Requires-Dist: pproxy>=2.7.9
33
+ Requires-Dist: pydantic>=2.0
34
+ Provides-Extra: dev
35
+ Requires-Dist: coverage[toml]; extra == 'dev'
36
+ Requires-Dist: pre-commit; extra == 'dev'
37
+ Requires-Dist: pyright; extra == 'dev'
38
+ Requires-Dist: pytest; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio; extra == 'dev'
40
+ Requires-Dist: pytest-cov; extra == 'dev'
41
+ Requires-Dist: respx; extra == 'dev'
42
+ Requires-Dist: ruff; extra == 'dev'
43
+ Description-Content-Type: text/markdown
44
+
45
+ [![PyPI version](https://badge.fury.io/py/eggpool.svg)](https://pypi.org/project/eggpool/)
46
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
48
+ [![CI](https://github.com/eggstack/eggpool/actions/workflows/ci.yml/badge.svg)](https://github.com/eggstack/eggpool/actions/workflows/ci.yml)
49
+
50
+ # EggPool
51
+
52
+ A lightweight, LAN-hosted proxy that aggregates multiple AI provider accounts behind one OpenAI/Anthropic-compatible endpoint.
53
+
54
+ ## Features
55
+
56
+ - Proxies model requests across multiple providers and accounts behind a single endpoint
57
+ - Supports OpenAI-compatible and Anthropic-compatible upstream request paths
58
+ - Dynamically discovers available models; routes by quota utilization
59
+ - Per-account outbound proxy support ([pproxy](https://pypi.org/project/pproxy/) — SOCKS5, HTTP, Shadowsocks)
60
+ - Tracks requests, tokens, latency, errors, and estimated costs in SQLite
61
+ - Multi-page dashboard with 50+ themes, reliability, routing, and runtime views
62
+ - Designed for lightweight deployments (Raspberry Pi, SBCs)
63
+
64
+ ## Quick Start
65
+
66
+ ```bash
67
+ # Install (one-shot)
68
+ curl -fsSL https://raw.githubusercontent.com/eggstack/eggpool/main/scripts/install.sh | bash
69
+
70
+ # Interactive onboarding — connect providers, validate, start
71
+ eggpool onboard
72
+
73
+ # Install as a systemd service
74
+ sudo env "PATH=$PATH" "$(command -v eggpool)" deploy systemd --install
75
+ ```
76
+
77
+ See [Deployment](docs/deployment.md) for alternative install methods (pipx, manual, production) and the full deployment guide.
78
+
79
+ ## CLI Reference
80
+
81
+ | Command | Description |
82
+ |---------|-------------|
83
+ | `eggpool serve` | Start the proxy server (`--daemon` to detach) |
84
+ | `eggpool onboard` | Interactive onboarding wizard |
85
+ | `eggpool connect` | Add a provider account interactively |
86
+ | `eggpool connect list` | List supported providers |
87
+ | `eggpool check-config` | Validate configuration |
88
+ | `eggpool migrate` | Run database migrations |
89
+ | `eggpool rehash` | Restart to apply config changes |
90
+ | `eggpool stop` | Stop the running server |
91
+ | `eggpool models refresh` | Refresh the model catalog |
92
+ | `eggpool accounts status` | Show configured account status |
93
+ | `eggpool runtime-status` | Print runtime health summary |
94
+ | `eggpool backup` | Create a timestamped backup |
95
+ | `eggpool recover` | Restore from a backup archive |
96
+ | `eggpool deploy systemd` | Install/manage systemd service |
97
+ | `eggpool deploy cron` | Install watchdog cron (non-systemd) |
98
+ | `eggpool update` | Check for and install updates |
99
+
100
+ All commands accept `--config /path/to/config.toml`. Config resolution: `--config` > `$EGGPOOL_CONFIG` > `~/.config/eggpool/config.toml` > `./config.toml`.
101
+
102
+ Full command reference: [docs/deployment.md](docs/deployment.md#deploy-commands-reference)
103
+
104
+ ## Configuration
105
+
106
+ Configuration lives in a single TOML file. API keys are loaded from environment variables or `.env`.
107
+
108
+ ```toml
109
+ # Example provider configuration
110
+ [providers.opencode-go]
111
+ id = "opencode-go"
112
+ base_url = "https://opencode.ai/zen/go/v1"
113
+ protocols = ["openai", "anthropic"]
114
+
115
+ [[providers.opencode-go.accounts]]
116
+ name = "personal"
117
+ api_key = "sk-your-opencode-go-key"
118
+ ```
119
+
120
+ Use `eggpool connect` for interactive provider setup. See [docs/providers.md](docs/providers.md) for the full provider catalog, configuration details, and troubleshooting.
121
+
122
+ ### Key Config Sections
123
+
124
+ | Section | Purpose |
125
+ |---------|---------|
126
+ | `[server]` | Bind address, port (default 11300), API key, logging, threads |
127
+ | `[upstream]` | Upstream API base URL, timeouts, connection pool |
128
+ | `[database]` | SQLite path, WAL mode |
129
+ | `[models]` | Catalog refresh, exposure mode, model collapse |
130
+ | `[routing]` | Routing strategy, retry limits, quota mode |
131
+ | `[dashboard]` | Dashboard toggle, theme, refresh interval |
132
+ | `[providers.*]` | Provider configs with accounts and routing priority |
133
+ | `[network]` | Outbound transport, DNS cache |
134
+
135
+ Full config reference: [`config.example.toml`](config.example.toml) | [docs/providers.md](docs/providers.md)
136
+
137
+ ## API Endpoints
138
+
139
+ | Method | Path | Description |
140
+ |--------|------|-------------|
141
+ | `GET` | `/v1/models` | List available models |
142
+ | `POST` | `/v1/chat/completions` | OpenAI-compatible chat completions |
143
+ | `POST` | `/v1/messages` | Anthropic-compatible messages |
144
+ | `GET` | `/v1/healthz` | Liveness check |
145
+ | `GET` | `/v1/readyz` | Readiness check |
146
+
147
+ When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, and more. Stats API available under `/api/stats/*`.
148
+
149
+ ## Documentation
150
+
151
+ | Topic | Link |
152
+ |-------|------|
153
+ | Deployment (install, systemd, production) | [docs/deployment.md](docs/deployment.md) |
154
+ | Provider catalog & configuration | [docs/providers.md](docs/providers.md) |
155
+ | Backup & restore | [docs/backup-restore.md](docs/backup-restore.md) |
156
+ | Per-account outbound proxy | [docs/proxy.md](docs/proxy.md) |
157
+ | Model context limits | [docs/model-limits.md](docs/model-limits.md) |
158
+ | Raspberry Pi setup | [docs/raspberry-pi.md](docs/raspberry-pi.md) |
159
+ | Firewall configuration | [docs/firewall.md](docs/firewall.md) |
160
+ | Filesystem layout | [docs/filesystem-layout.md](docs/filesystem-layout.md) |
161
+ | Network & DNS diagnostics | [docs/network-diagnostics.md](docs/network-diagnostics.md) |
162
+
163
+ ## Development
164
+
165
+ ```bash
166
+ uv sync --extra dev
167
+ uv run ruff check src/ tests/ scripts/
168
+ uv run ruff format src/ tests/ scripts/
169
+ uv run pyright src/ scripts/
170
+ uv run pytest
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,131 @@
1
+ [![PyPI version](https://badge.fury.io/py/eggpool.svg)](https://pypi.org/project/eggpool/)
2
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ [![CI](https://github.com/eggstack/eggpool/actions/workflows/ci.yml/badge.svg)](https://github.com/eggstack/eggpool/actions/workflows/ci.yml)
5
+
6
+ # EggPool
7
+
8
+ A lightweight, LAN-hosted proxy that aggregates multiple AI provider accounts behind one OpenAI/Anthropic-compatible endpoint.
9
+
10
+ ## Features
11
+
12
+ - Proxies model requests across multiple providers and accounts behind a single endpoint
13
+ - Supports OpenAI-compatible and Anthropic-compatible upstream request paths
14
+ - Dynamically discovers available models; routes by quota utilization
15
+ - Per-account outbound proxy support ([pproxy](https://pypi.org/project/pproxy/) — SOCKS5, HTTP, Shadowsocks)
16
+ - Tracks requests, tokens, latency, errors, and estimated costs in SQLite
17
+ - Multi-page dashboard with 50+ themes, reliability, routing, and runtime views
18
+ - Designed for lightweight deployments (Raspberry Pi, SBCs)
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ # Install (one-shot)
24
+ curl -fsSL https://raw.githubusercontent.com/eggstack/eggpool/main/scripts/install.sh | bash
25
+
26
+ # Interactive onboarding — connect providers, validate, start
27
+ eggpool onboard
28
+
29
+ # Install as a systemd service
30
+ sudo env "PATH=$PATH" "$(command -v eggpool)" deploy systemd --install
31
+ ```
32
+
33
+ See [Deployment](docs/deployment.md) for alternative install methods (pipx, manual, production) and the full deployment guide.
34
+
35
+ ## CLI Reference
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | `eggpool serve` | Start the proxy server (`--daemon` to detach) |
40
+ | `eggpool onboard` | Interactive onboarding wizard |
41
+ | `eggpool connect` | Add a provider account interactively |
42
+ | `eggpool connect list` | List supported providers |
43
+ | `eggpool check-config` | Validate configuration |
44
+ | `eggpool migrate` | Run database migrations |
45
+ | `eggpool rehash` | Restart to apply config changes |
46
+ | `eggpool stop` | Stop the running server |
47
+ | `eggpool models refresh` | Refresh the model catalog |
48
+ | `eggpool accounts status` | Show configured account status |
49
+ | `eggpool runtime-status` | Print runtime health summary |
50
+ | `eggpool backup` | Create a timestamped backup |
51
+ | `eggpool recover` | Restore from a backup archive |
52
+ | `eggpool deploy systemd` | Install/manage systemd service |
53
+ | `eggpool deploy cron` | Install watchdog cron (non-systemd) |
54
+ | `eggpool update` | Check for and install updates |
55
+
56
+ All commands accept `--config /path/to/config.toml`. Config resolution: `--config` > `$EGGPOOL_CONFIG` > `~/.config/eggpool/config.toml` > `./config.toml`.
57
+
58
+ Full command reference: [docs/deployment.md](docs/deployment.md#deploy-commands-reference)
59
+
60
+ ## Configuration
61
+
62
+ Configuration lives in a single TOML file. API keys are loaded from environment variables or `.env`.
63
+
64
+ ```toml
65
+ # Example provider configuration
66
+ [providers.opencode-go]
67
+ id = "opencode-go"
68
+ base_url = "https://opencode.ai/zen/go/v1"
69
+ protocols = ["openai", "anthropic"]
70
+
71
+ [[providers.opencode-go.accounts]]
72
+ name = "personal"
73
+ api_key = "sk-your-opencode-go-key"
74
+ ```
75
+
76
+ Use `eggpool connect` for interactive provider setup. See [docs/providers.md](docs/providers.md) for the full provider catalog, configuration details, and troubleshooting.
77
+
78
+ ### Key Config Sections
79
+
80
+ | Section | Purpose |
81
+ |---------|---------|
82
+ | `[server]` | Bind address, port (default 11300), API key, logging, threads |
83
+ | `[upstream]` | Upstream API base URL, timeouts, connection pool |
84
+ | `[database]` | SQLite path, WAL mode |
85
+ | `[models]` | Catalog refresh, exposure mode, model collapse |
86
+ | `[routing]` | Routing strategy, retry limits, quota mode |
87
+ | `[dashboard]` | Dashboard toggle, theme, refresh interval |
88
+ | `[providers.*]` | Provider configs with accounts and routing priority |
89
+ | `[network]` | Outbound transport, DNS cache |
90
+
91
+ Full config reference: [`config.example.toml`](config.example.toml) | [docs/providers.md](docs/providers.md)
92
+
93
+ ## API Endpoints
94
+
95
+ | Method | Path | Description |
96
+ |--------|------|-------------|
97
+ | `GET` | `/v1/models` | List available models |
98
+ | `POST` | `/v1/chat/completions` | OpenAI-compatible chat completions |
99
+ | `POST` | `/v1/messages` | Anthropic-compatible messages |
100
+ | `GET` | `/v1/healthz` | Liveness check |
101
+ | `GET` | `/v1/readyz` | Readiness check |
102
+
103
+ When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, and more. Stats API available under `/api/stats/*`.
104
+
105
+ ## Documentation
106
+
107
+ | Topic | Link |
108
+ |-------|------|
109
+ | Deployment (install, systemd, production) | [docs/deployment.md](docs/deployment.md) |
110
+ | Provider catalog & configuration | [docs/providers.md](docs/providers.md) |
111
+ | Backup & restore | [docs/backup-restore.md](docs/backup-restore.md) |
112
+ | Per-account outbound proxy | [docs/proxy.md](docs/proxy.md) |
113
+ | Model context limits | [docs/model-limits.md](docs/model-limits.md) |
114
+ | Raspberry Pi setup | [docs/raspberry-pi.md](docs/raspberry-pi.md) |
115
+ | Firewall configuration | [docs/firewall.md](docs/firewall.md) |
116
+ | Filesystem layout | [docs/filesystem-layout.md](docs/filesystem-layout.md) |
117
+ | Network & DNS diagnostics | [docs/network-diagnostics.md](docs/network-diagnostics.md) |
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ uv sync --extra dev
123
+ uv run ruff check src/ tests/ scripts/
124
+ uv run ruff format src/ tests/ scripts/
125
+ uv run pyright src/ scripts/
126
+ uv run pytest
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT
@@ -457,9 +457,15 @@ Production (`eggpool deploy systemd --install --production`):
457
457
 
458
458
  ## Background Tasks
459
459
 
460
- `TaskSupervisor` (`background/__init__.py`) manages long-running loops with restart-on-failure and exponential backoff:
461
- - Catalog refresh (configurable interval, default 300s)
462
- - Retention cleanup (old requests, events, pings)
463
- - Periodic checkpoint
464
- - Usage window refresh
465
- - Reservation expiry reconciliation
460
+ `TaskSupervisor` (`background/__init__.py`) manages long-running loops with restart-on-failure and exponential backoff. All tasks are registered in `app.py` during lifespan setup:
461
+
462
+ | Task | Condition | Description |
463
+ |------|-----------|-------------|
464
+ | `catalog_refresh` | `refresh_interval_s > 0` | Periodic upstream model catalog refresh |
465
+ | `retention_cleanup` | Always | Hourly cleanup of old requests, events, pings, rollups, and expired reservations |
466
+ | `checkpoint` | Always | Periodic SQLite WAL checkpoint (every 4h) |
467
+ | `usage_window_refresh` | Always | Refreshes persisted usage windows every 60s |
468
+ | `stale_request_finalizer` | Always | Safety net for leaked streaming requests (every 60s) |
469
+ | `metrics_flush` | `write_mode != "immediate"` | Buffered analytics flush to `usage_rollups` |
470
+ | `update_checker` | Always | Periodic PyPI update check (default 24h); see `update_checker.py` |
471
+ | `automatic_backup` | `backup.enabled` | In-process SQLite backup with count-based retention; see `background/backup.py` |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eggpool"
3
- version = "0.3.4"
3
+ version = "0.3.5"
4
4
  description = "A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,7 +31,7 @@ def _failure_backoff(consecutive_failures: int) -> float:
31
31
  )
32
32
 
33
33
 
34
- @dataclass
34
+ @dataclass(slots=True)
35
35
  class AccountRuntimeState:
36
36
  """Mutable runtime state for an account."""
37
37
 
@@ -1125,23 +1125,20 @@ def create_app(
1125
1125
  def __init__(self, max_size: int = 3, ttl_s: int = 300) -> None:
1126
1126
  self._max_size = max_size
1127
1127
  self._ttl_s = ttl_s
1128
- self._cache: dict[str, tuple[str, float]] = {}
1129
- self._last_used: str = ""
1130
-
1131
- def get(self, theme_name: str) -> str | None:
1132
- if theme_name in self._cache:
1133
- css, ts = self._cache[theme_name]
1134
- if (
1135
- time.monotonic() - ts < self._ttl_s
1136
- or theme_name == self._last_used
1137
- ):
1138
- self._last_used = theme_name
1128
+ self._cache: dict[tuple[str, str | None], tuple[str, float]] = {}
1129
+ self._last_used: tuple[str, str | None] | None = None
1130
+
1131
+ def get(self, key: tuple[str, str | None]) -> str | None:
1132
+ if key in self._cache:
1133
+ css, ts = self._cache[key]
1134
+ if time.monotonic() - ts < self._ttl_s or key == self._last_used:
1135
+ self._last_used = key
1139
1136
  return css
1140
- del self._cache[theme_name]
1137
+ del self._cache[key]
1141
1138
  return None
1142
1139
 
1143
- def put(self, theme_name: str, css: str) -> None:
1144
- if len(self._cache) >= self._max_size and theme_name not in self._cache:
1140
+ def put(self, key: tuple[str, str | None], css: str) -> None:
1141
+ if len(self._cache) >= self._max_size and key not in self._cache:
1145
1142
  now = time.monotonic()
1146
1143
  to_evict = [
1147
1144
  k
@@ -1157,15 +1154,17 @@ def create_app(
1157
1154
  )
1158
1155
  if oldest != self._last_used:
1159
1156
  del self._cache[oldest]
1160
- self._cache[theme_name] = (css, time.monotonic())
1161
- self._last_used = theme_name
1157
+ self._cache[key] = (css, time.monotonic())
1158
+ self._last_used = key
1162
1159
 
1163
1160
  _theme_css_cache = _ThemeCssCache()
1164
1161
 
1165
1162
  @app.get("/static/theme.css")
1166
1163
  async def theme_css(request: Request) -> Response: # pyright: ignore[reportUnusedFunction]
1167
1164
  theme_name = request.query_params.get("theme", "default")
1168
- cached = _theme_css_cache.get(theme_name)
1165
+ themes_dir = config.dashboard.themes_dir
1166
+ cache_key = (theme_name, themes_dir)
1167
+ cached = _theme_css_cache.get(cache_key)
1169
1168
  if cached is not None:
1170
1169
  return Response(
1171
1170
  content=cached,
@@ -1174,8 +1173,8 @@ def create_app(
1174
1173
  )
1175
1174
  from eggpool.dashboard.render import get_theme_css
1176
1175
 
1177
- css = get_theme_css(theme_name)
1178
- _theme_css_cache.put(theme_name, css)
1176
+ css = get_theme_css(theme_name, themes_dir)
1177
+ _theme_css_cache.put(cache_key, css)
1179
1178
  return Response(
1180
1179
  content=css,
1181
1180
  media_type="text/css",