@alumbwe/anvil 1.0.20 → 1.0.21

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 (328) hide show
  1. package/package.json +6 -2
  2. package/src/agents/bundled-agents.generated.ts +4 -4
  3. package/src/chat.tsx +1703 -1698
  4. package/src/cli-args.ts +134 -134
  5. package/src/components/status-bar.tsx +331 -313
  6. package/src/hooks/helpers/send-message.ts +663 -654
  7. package/src/hooks/use-send-message.ts +806 -773
  8. package/src/index.tsx +473 -473
  9. package/src/state/chat-store.ts +553 -544
  10. package/src/utils/create-run-config.ts +121 -121
  11. package/src/utils/format-compact-number.ts +37 -0
  12. package/vendor/@anvil/common/package.json +27 -0
  13. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +846 -0
  14. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +101 -0
  15. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +1408 -0
  16. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +258 -0
  17. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +48 -0
  18. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +282 -0
  19. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +526 -0
  20. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +68 -0
  21. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +132 -0
  22. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +422 -0
  23. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +167 -0
  24. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +145 -0
  25. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +234 -0
  26. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +379 -0
  27. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +807 -0
  28. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +154 -0
  29. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +246 -0
  30. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +77 -0
  31. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +89 -0
  32. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +298 -0
  33. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +116 -0
  34. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +167 -0
  35. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +132 -0
  36. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +72 -0
  37. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +30 -0
  38. package/vendor/@anvil/common/src/actions.ts +215 -0
  39. package/vendor/@anvil/common/src/analytics-core.ts +69 -0
  40. package/vendor/@anvil/common/src/analytics.ts +93 -0
  41. package/vendor/@anvil/common/src/api-keys/constants.ts +26 -0
  42. package/vendor/@anvil/common/src/browser-actions.ts +413 -0
  43. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +63 -0
  44. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +246 -0
  45. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +106 -0
  46. package/vendor/@anvil/common/src/constants/agents.ts +99 -0
  47. package/vendor/@anvil/common/src/constants/analytics-events.ts +435 -0
  48. package/vendor/@anvil/common/src/constants/anthropic.ts +73 -0
  49. package/vendor/@anvil/common/src/constants/anvil-data-use.ts +81 -0
  50. package/vendor/@anvil/common/src/constants/anvil-errors.ts +7 -0
  51. package/vendor/@anvil/common/src/constants/anvil-gemini-thinker.ts +21 -0
  52. package/vendor/@anvil/common/src/constants/anvil-model-ids.ts +11 -0
  53. package/vendor/@anvil/common/src/constants/anvil-models.ts +2130 -0
  54. package/vendor/@anvil/common/src/constants/anvil-onboarding-gate.ts +55 -0
  55. package/vendor/@anvil/common/src/constants/anvil-onboarding.ts +307 -0
  56. package/vendor/@anvil/common/src/constants/anvil-referral-tiers.ts +87 -0
  57. package/vendor/@anvil/common/src/constants/anvil-signup-block.ts +68 -0
  58. package/vendor/@anvil/common/src/constants/anvil-spend-ceilings.ts +450 -0
  59. package/vendor/@anvil/common/src/constants/anvil-trust.ts +976 -0
  60. package/vendor/@anvil/common/src/constants/auth.ts +18 -0
  61. package/vendor/@anvil/common/src/constants/byok.ts +2 -0
  62. package/vendor/@anvil/common/src/constants/cf-worker-signals.ts +132 -0
  63. package/vendor/@anvil/common/src/constants/composio.ts +34 -0
  64. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +55 -0
  65. package/vendor/@anvil/common/src/constants/feedback.ts +13 -0
  66. package/vendor/@anvil/common/src/constants/foreign-client-signals.ts +272 -0
  67. package/vendor/@anvil/common/src/constants/free-agents.ts +777 -0
  68. package/vendor/@anvil/common/src/constants/gemini.ts +15 -0
  69. package/vendor/@anvil/common/src/constants/grant-priorities.ts +13 -0
  70. package/vendor/@anvil/common/src/constants/hosts.ts +6 -0
  71. package/vendor/@anvil/common/src/constants/images.ts +51 -0
  72. package/vendor/@anvil/common/src/constants/index.ts +7 -0
  73. package/vendor/@anvil/common/src/constants/knowledge.ts +35 -0
  74. package/vendor/@anvil/common/src/constants/limits.ts +23 -0
  75. package/vendor/@anvil/common/src/constants/model-config.ts +277 -0
  76. package/vendor/@anvil/common/src/constants/openrouter-attribution.ts +8 -0
  77. package/vendor/@anvil/common/src/constants/paths.ts +69 -0
  78. package/vendor/@anvil/common/src/constants/provider-routes.ts +317 -0
  79. package/vendor/@anvil/common/src/constants/reasoning-effort.ts +79 -0
  80. package/vendor/@anvil/common/src/constants/skills.ts +60 -0
  81. package/vendor/@anvil/common/src/constants/subscription-plans.ts +49 -0
  82. package/vendor/@anvil/common/src/constants/ui.ts +25 -0
  83. package/vendor/@anvil/common/src/env-ci.ts +36 -0
  84. package/vendor/@anvil/common/src/env-process.ts +95 -0
  85. package/vendor/@anvil/common/src/env-schema.ts +95 -0
  86. package/vendor/@anvil/common/src/env.ts +24 -0
  87. package/vendor/@anvil/common/src/gravity-capi.ts +207 -0
  88. package/vendor/@anvil/common/src/mcp/client.ts +233 -0
  89. package/vendor/@anvil/common/src/old-constants.ts +10 -0
  90. package/vendor/@anvil/common/src/project-file-tree.ts +355 -0
  91. package/vendor/@anvil/common/src/reddit-capi.ts +254 -0
  92. package/vendor/@anvil/common/src/schemas/feedback.ts +52 -0
  93. package/vendor/@anvil/common/src/schemas/logs.ts +66 -0
  94. package/vendor/@anvil/common/src/templates/agent-validation.ts +392 -0
  95. package/vendor/@anvil/common/src/templates/initial-agents-dir/LICENSE +202 -0
  96. package/vendor/@anvil/common/src/templates/initial-agents-dir/README.md +294 -0
  97. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/01-basic-diff-reviewer.ts +17 -0
  98. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/02-intermediate-git-committer.ts +78 -0
  99. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/03-advanced-file-explorer.ts +73 -0
  100. package/vendor/@anvil/common/src/templates/initial-agents-dir/my-custom-agent.ts +40 -0
  101. package/vendor/@anvil/common/src/templates/initial-agents-dir/package.json +6 -0
  102. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/README.md +65 -0
  103. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/example-skill/SKILL.md +29 -0
  104. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/agent-definition.ts +497 -0
  105. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/tools.ts +444 -0
  106. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/util-types.ts +178 -0
  107. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +351 -0
  108. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +169 -0
  109. package/vendor/@anvil/common/src/testing/errors.ts +33 -0
  110. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +334 -0
  111. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +6 -0
  112. package/vendor/@anvil/common/src/testing/index.ts +84 -0
  113. package/vendor/@anvil/common/src/testing/mock-modules.ts +53 -0
  114. package/vendor/@anvil/common/src/testing/mock-types.ts +123 -0
  115. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +261 -0
  116. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +93 -0
  117. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +218 -0
  118. package/vendor/@anvil/common/src/testing/mocks/database.ts +337 -0
  119. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +219 -0
  120. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +166 -0
  121. package/vendor/@anvil/common/src/testing/mocks/index.ts +101 -0
  122. package/vendor/@anvil/common/src/testing/mocks/logger.ts +135 -0
  123. package/vendor/@anvil/common/src/testing/mocks/stream.ts +313 -0
  124. package/vendor/@anvil/common/src/testing/mocks/timers.ts +132 -0
  125. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +127 -0
  126. package/vendor/@anvil/common/src/testing/setup.ts +282 -0
  127. package/vendor/@anvil/common/src/testing-env-ci.ts +15 -0
  128. package/vendor/@anvil/common/src/testing-env-process.ts +78 -0
  129. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +34 -0
  130. package/vendor/@anvil/common/src/tools/compile-tool-definitions.ts +157 -0
  131. package/vendor/@anvil/common/src/tools/constants.ts +117 -0
  132. package/vendor/@anvil/common/src/tools/list.ts +190 -0
  133. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +213 -0
  134. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +47 -0
  135. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +65 -0
  136. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +49 -0
  137. package/vendor/@anvil/common/src/tools/params/tool/add-message.ts +39 -0
  138. package/vendor/@anvil/common/src/tools/params/tool/add-subgoal.ts +57 -0
  139. package/vendor/@anvil/common/src/tools/params/tool/apply-patch.ts +110 -0
  140. package/vendor/@anvil/common/src/tools/params/tool/ask-user.ts +181 -0
  141. package/vendor/@anvil/common/src/tools/params/tool/browser-logs.ts +85 -0
  142. package/vendor/@anvil/common/src/tools/params/tool/cloud-plan-ready.ts +94 -0
  143. package/vendor/@anvil/common/src/tools/params/tool/code-search.ts +159 -0
  144. package/vendor/@anvil/common/src/tools/params/tool/composio.ts +131 -0
  145. package/vendor/@anvil/common/src/tools/params/tool/create-plan.ts +80 -0
  146. package/vendor/@anvil/common/src/tools/params/tool/end-turn.ts +57 -0
  147. package/vendor/@anvil/common/src/tools/params/tool/find-files.ts +60 -0
  148. package/vendor/@anvil/common/src/tools/params/tool/glob.ts +80 -0
  149. package/vendor/@anvil/common/src/tools/params/tool/gravity-index.ts +93 -0
  150. package/vendor/@anvil/common/src/tools/params/tool/list-directory.ts +58 -0
  151. package/vendor/@anvil/common/src/tools/params/tool/lookup-agent-info.ts +37 -0
  152. package/vendor/@anvil/common/src/tools/params/tool/propose-str-replace.ts +103 -0
  153. package/vendor/@anvil/common/src/tools/params/tool/propose-write-file.ts +71 -0
  154. package/vendor/@anvil/common/src/tools/params/tool/read-docs.ts +90 -0
  155. package/vendor/@anvil/common/src/tools/params/tool/read-files.ts +109 -0
  156. package/vendor/@anvil/common/src/tools/params/tool/read-subtree.ts +79 -0
  157. package/vendor/@anvil/common/src/tools/params/tool/read-url.ts +81 -0
  158. package/vendor/@anvil/common/src/tools/params/tool/render-ui.ts +168 -0
  159. package/vendor/@anvil/common/src/tools/params/tool/run-file-change-hooks.ts +57 -0
  160. package/vendor/@anvil/common/src/tools/params/tool/run-terminal-command.ts +211 -0
  161. package/vendor/@anvil/common/src/tools/params/tool/screenshot.ts +97 -0
  162. package/vendor/@anvil/common/src/tools/params/tool/set-messages.ts +44 -0
  163. package/vendor/@anvil/common/src/tools/params/tool/set-output.ts +61 -0
  164. package/vendor/@anvil/common/src/tools/params/tool/skill.ts +59 -0
  165. package/vendor/@anvil/common/src/tools/params/tool/spawn-agent-inline.ts +56 -0
  166. package/vendor/@anvil/common/src/tools/params/tool/spawn-agents.ts +154 -0
  167. package/vendor/@anvil/common/src/tools/params/tool/str-replace.ts +107 -0
  168. package/vendor/@anvil/common/src/tools/params/tool/suggest-followups.ts +97 -0
  169. package/vendor/@anvil/common/src/tools/params/tool/task-completed.ts +61 -0
  170. package/vendor/@anvil/common/src/tools/params/tool/think-deeply.ts +56 -0
  171. package/vendor/@anvil/common/src/tools/params/tool/update-subgoal.ts +89 -0
  172. package/vendor/@anvil/common/src/tools/params/tool/vision-analyze.ts +82 -0
  173. package/vendor/@anvil/common/src/tools/params/tool/web-search.ts +73 -0
  174. package/vendor/@anvil/common/src/tools/params/tool/write-file.ts +71 -0
  175. package/vendor/@anvil/common/src/tools/params/tool/write-todos.ts +67 -0
  176. package/vendor/@anvil/common/src/tools/params/utils.ts +150 -0
  177. package/vendor/@anvil/common/src/tools/utils.ts +24 -0
  178. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +20 -0
  179. package/vendor/@anvil/common/src/types/agent-template.ts +215 -0
  180. package/vendor/@anvil/common/src/types/anvil-session.ts +535 -0
  181. package/vendor/@anvil/common/src/types/anvil-streak.ts +6 -0
  182. package/vendor/@anvil/common/src/types/anvil-usage.ts +64 -0
  183. package/vendor/@anvil/common/src/types/api/agents/publish.ts +61 -0
  184. package/vendor/@anvil/common/src/types/bun-test.d.ts +5 -0
  185. package/vendor/@anvil/common/src/types/contracts/agent-runtime.ts +75 -0
  186. package/vendor/@anvil/common/src/types/contracts/analytics.ts +9 -0
  187. package/vendor/@anvil/common/src/types/contracts/bigquery.ts +55 -0
  188. package/vendor/@anvil/common/src/types/contracts/billing.ts +46 -0
  189. package/vendor/@anvil/common/src/types/contracts/client.ts +53 -0
  190. package/vendor/@anvil/common/src/types/contracts/database.ts +112 -0
  191. package/vendor/@anvil/common/src/types/contracts/env.ts +203 -0
  192. package/vendor/@anvil/common/src/types/contracts/llm.ts +191 -0
  193. package/vendor/@anvil/common/src/types/contracts/logger.ts +14 -0
  194. package/vendor/@anvil/common/src/types/contracts/logs.ts +34 -0
  195. package/vendor/@anvil/common/src/types/contracts/trace.ts +21 -0
  196. package/vendor/@anvil/common/src/types/dynamic-agent-template.ts +328 -0
  197. package/vendor/@anvil/common/src/types/filesystem.ts +10 -0
  198. package/vendor/@anvil/common/src/types/function-params.ts +33 -0
  199. package/vendor/@anvil/common/src/types/grant.ts +20 -0
  200. package/vendor/@anvil/common/src/types/gravity-index.ts +170 -0
  201. package/vendor/@anvil/common/src/types/json.ts +29 -0
  202. package/vendor/@anvil/common/src/types/mcp.ts +24 -0
  203. package/vendor/@anvil/common/src/types/messages/anvil-message.ts +58 -0
  204. package/vendor/@anvil/common/src/types/messages/content-part.ts +59 -0
  205. package/vendor/@anvil/common/src/types/messages/data-content.ts +14 -0
  206. package/vendor/@anvil/common/src/types/messages/provider-metadata.ts +13 -0
  207. package/vendor/@anvil/common/src/types/organization.ts +118 -0
  208. package/vendor/@anvil/common/src/types/print-mode.ts +121 -0
  209. package/vendor/@anvil/common/src/types/publisher.ts +67 -0
  210. package/vendor/@anvil/common/src/types/session-state.ts +149 -0
  211. package/vendor/@anvil/common/src/types/skill.ts +86 -0
  212. package/vendor/@anvil/common/src/types/source.ts +11 -0
  213. package/vendor/@anvil/common/src/types/spawn.ts +13 -0
  214. package/vendor/@anvil/common/src/types/subscription.ts +67 -0
  215. package/vendor/@anvil/common/src/types/usage.ts +16 -0
  216. package/vendor/@anvil/common/src/types/util.ts +3 -0
  217. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +26 -0
  218. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +122 -0
  219. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +102 -0
  220. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +143 -0
  221. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +202 -0
  222. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +193 -0
  223. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +140 -0
  224. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +148 -0
  225. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +115 -0
  226. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +57 -0
  227. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +774 -0
  228. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +206 -0
  229. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +173 -0
  230. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +60 -0
  231. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +31 -0
  232. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +1252 -0
  233. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +505 -0
  234. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +22 -0
  235. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +90 -0
  236. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +325 -0
  237. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +28 -0
  238. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +130 -0
  239. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +1008 -0
  240. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +289 -0
  241. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +239 -0
  242. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +70 -0
  243. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +108 -0
  244. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +180 -0
  245. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +24 -0
  246. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +88 -0
  247. package/vendor/@anvil/common/src/util/ad-user-agent.ts +25 -0
  248. package/vendor/@anvil/common/src/util/agent-file-utils.ts +110 -0
  249. package/vendor/@anvil/common/src/util/agent-id-parsing.ts +136 -0
  250. package/vendor/@anvil/common/src/util/agent-name-normalization.ts +38 -0
  251. package/vendor/@anvil/common/src/util/agent-name-resolver.ts +86 -0
  252. package/vendor/@anvil/common/src/util/analytics-dispatcher.ts +82 -0
  253. package/vendor/@anvil/common/src/util/analytics-log.ts +78 -0
  254. package/vendor/@anvil/common/src/util/analytics-sampling.ts +255 -0
  255. package/vendor/@anvil/common/src/util/anvil-model-availability.ts +102 -0
  256. package/vendor/@anvil/common/src/util/anvil-privacy.ts +82 -0
  257. package/vendor/@anvil/common/src/util/anvil-streak-line.ts +103 -0
  258. package/vendor/@anvil/common/src/util/anvil-streak.ts +156 -0
  259. package/vendor/@anvil/common/src/util/anvil-usage-summary.ts +125 -0
  260. package/vendor/@anvil/common/src/util/array.ts +31 -0
  261. package/vendor/@anvil/common/src/util/axiom-only-log.ts +124 -0
  262. package/vendor/@anvil/common/src/util/cache-debug.ts +171 -0
  263. package/vendor/@anvil/common/src/util/client-user-agent.ts +114 -0
  264. package/vendor/@anvil/common/src/util/credentials.ts +26 -0
  265. package/vendor/@anvil/common/src/util/currency.ts +25 -0
  266. package/vendor/@anvil/common/src/util/dates.ts +81 -0
  267. package/vendor/@anvil/common/src/util/disposable-email.ts +277 -0
  268. package/vendor/@anvil/common/src/util/engagement-tracker.ts +129 -0
  269. package/vendor/@anvil/common/src/util/env-file-path.ts +40 -0
  270. package/vendor/@anvil/common/src/util/error.ts +562 -0
  271. package/vendor/@anvil/common/src/util/file-read-limits.ts +236 -0
  272. package/vendor/@anvil/common/src/util/file.ts +339 -0
  273. package/vendor/@anvil/common/src/util/format-code-search.ts +115 -0
  274. package/vendor/@anvil/common/src/util/lazy-response-ads.ts +92 -0
  275. package/vendor/@anvil/common/src/util/log-data.ts +57 -0
  276. package/vendor/@anvil/common/src/util/log-ingest.ts +58 -0
  277. package/vendor/@anvil/common/src/util/log-mirror.ts +34 -0
  278. package/vendor/@anvil/common/src/util/lru-cache.ts +67 -0
  279. package/vendor/@anvil/common/src/util/messages.ts +685 -0
  280. package/vendor/@anvil/common/src/util/min-heap.ts +87 -0
  281. package/vendor/@anvil/common/src/util/model-utils.ts +4 -0
  282. package/vendor/@anvil/common/src/util/object.ts +128 -0
  283. package/vendor/@anvil/common/src/util/partial-json-delta.ts +89 -0
  284. package/vendor/@anvil/common/src/util/path.ts +15 -0
  285. package/vendor/@anvil/common/src/util/project-ignore.ts +119 -0
  286. package/vendor/@anvil/common/src/util/promise.ts +73 -0
  287. package/vendor/@anvil/common/src/util/random.ts +15 -0
  288. package/vendor/@anvil/common/src/util/rate-limit.ts +56 -0
  289. package/vendor/@anvil/common/src/util/reddit-anvil-retention.ts +60 -0
  290. package/vendor/@anvil/common/src/util/reddit-capi-events.ts +36 -0
  291. package/vendor/@anvil/common/src/util/response-ad-positions.ts +54 -0
  292. package/vendor/@anvil/common/src/util/saxy.ts +741 -0
  293. package/vendor/@anvil/common/src/util/skills.test.ts +44 -0
  294. package/vendor/@anvil/common/src/util/skills.ts +36 -0
  295. package/vendor/@anvil/common/src/util/split-data.ts +259 -0
  296. package/vendor/@anvil/common/src/util/stop-sequence.ts +61 -0
  297. package/vendor/@anvil/common/src/util/string.ts +426 -0
  298. package/vendor/@anvil/common/src/util/system-info.ts +53 -0
  299. package/vendor/@anvil/common/src/util/thread-title.ts +46 -0
  300. package/vendor/@anvil/common/src/util/ttft-histogram.ts +73 -0
  301. package/vendor/@anvil/common/src/util/xml-parser.ts +27 -0
  302. package/vendor/@anvil/common/src/util/xml.ts +17 -0
  303. package/vendor/@anvil/common/src/util/zod-schema.ts +25 -0
  304. package/vendor/@anvil/common/src/util/zoned-time.ts +136 -0
  305. package/vendor/@anvil/common/src/utils/ask-user-bridge.ts +44 -0
  306. package/vendor/@anvil/sdk/dist/index.cjs +52297 -0
  307. package/vendor/@anvil/sdk/dist/index.cjs.map +440 -0
  308. package/vendor/@anvil/sdk/dist/index.d.ts +4277 -0
  309. package/vendor/@anvil/sdk/dist/index.mjs +52290 -0
  310. package/vendor/@anvil/sdk/dist/index.mjs.map +440 -0
  311. package/vendor/@anvil/sdk/dist/vendor/ai.cjs +30886 -0
  312. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  313. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  314. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  315. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-linux/rg +0 -0
  316. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
  317. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-c-sharp.wasm +0 -0
  318. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-cpp.wasm +0 -0
  319. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-go.wasm +0 -0
  320. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-java.wasm +0 -0
  321. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-javascript.wasm +0 -0
  322. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-python.wasm +0 -0
  323. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-ruby.wasm +0 -0
  324. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-rust.wasm +0 -0
  325. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-tsx.wasm +0 -0
  326. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-typescript.wasm +0 -0
  327. package/vendor/@anvil/sdk/dist/wasm/tree-sitter.wasm +0 -0
  328. package/vendor/@anvil/sdk/package.json +21 -0
@@ -0,0 +1,171 @@
1
+ import type { JSONValue } from '../types/json'
2
+
3
+ type SerializableValue = JSONValue
4
+
5
+ type SerializableRecord = Record<string, SerializableValue>
6
+
7
+ export type CacheDebugCorrelation = {
8
+ projectRoot: string
9
+ filename: string
10
+ snapshotId: string
11
+ }
12
+
13
+ function normalizeForJson(value: unknown): SerializableValue {
14
+ if (
15
+ value === null ||
16
+ typeof value === 'string' ||
17
+ typeof value === 'number' ||
18
+ typeof value === 'boolean'
19
+ ) {
20
+ return value
21
+ }
22
+
23
+ if (value instanceof URL) {
24
+ return value.toString()
25
+ }
26
+
27
+ if (value instanceof Uint8Array) {
28
+ return {
29
+ type: 'Uint8Array',
30
+ byteLength: value.byteLength,
31
+ }
32
+ }
33
+
34
+ if (Array.isArray(value)) {
35
+ return value.map((item) => normalizeForJson(item))
36
+ }
37
+
38
+ if (typeof value === 'object') {
39
+ return Object.fromEntries(
40
+ Object.entries(value as Record<string, unknown>).map(([key, entryValue]) => [
41
+ key,
42
+ normalizeForJson(entryValue),
43
+ ]),
44
+ )
45
+ }
46
+
47
+ return String(value)
48
+ }
49
+
50
+ function summarizeDataUrl(value: string): SerializableValue {
51
+ const firstComma = value.indexOf(',')
52
+ const header = firstComma >= 0 ? value.slice(0, firstComma) : value
53
+ const payload = firstComma >= 0 ? value.slice(firstComma + 1) : ''
54
+ return {
55
+ type: 'data-url',
56
+ mediaType: header.slice(5).split(';')[0] || 'unknown',
57
+ payloadLength: payload.length,
58
+ preview: payload.slice(0, 32),
59
+ }
60
+ }
61
+
62
+ function summarizeLargeValue(value: SerializableValue): SerializableValue {
63
+ if (Array.isArray(value)) {
64
+ return value.map((item) => summarizeLargeValue(item))
65
+ }
66
+
67
+ if (!value || typeof value !== 'object') {
68
+ if (typeof value === 'string' && value.startsWith('data:')) {
69
+ return summarizeDataUrl(value)
70
+ }
71
+ return value
72
+ }
73
+
74
+ if ('url' in value && typeof value.url === 'string' && value.url.startsWith('data:')) {
75
+ return {
76
+ ...value,
77
+ url: summarizeDataUrl(value.url),
78
+ }
79
+ }
80
+
81
+ return Object.fromEntries(
82
+ Object.entries(value).map(([key, entryValue]) => {
83
+ if (key === 'file_data' && typeof entryValue === 'string' && entryValue.startsWith('data:')) {
84
+ return [key, summarizeDataUrl(entryValue)]
85
+ }
86
+ if (key === 'arguments' && typeof entryValue === 'string') {
87
+ return [key, entryValue]
88
+ }
89
+ return [
90
+ key,
91
+ entryValue === undefined ? undefined : summarizeLargeValue(entryValue),
92
+ ]
93
+ }),
94
+ )
95
+ }
96
+
97
+ function parseRequestBody(body: unknown): unknown {
98
+ if (typeof body !== 'string') {
99
+ return body
100
+ }
101
+
102
+ try {
103
+ return JSON.parse(body)
104
+ } catch {
105
+ return body
106
+ }
107
+ }
108
+
109
+ export function serializeCacheDebugCorrelation(
110
+ correlation: CacheDebugCorrelation,
111
+ ): string {
112
+ return JSON.stringify(correlation)
113
+ }
114
+
115
+ export function parseCacheDebugCorrelation(
116
+ value: unknown,
117
+ ): CacheDebugCorrelation | undefined {
118
+ if (typeof value !== 'string') {
119
+ return undefined
120
+ }
121
+
122
+ try {
123
+ const parsed = JSON.parse(value) as Partial<CacheDebugCorrelation>
124
+ if (
125
+ typeof parsed.projectRoot === 'string' &&
126
+ typeof parsed.filename === 'string' &&
127
+ typeof parsed.snapshotId === 'string'
128
+ ) {
129
+ return {
130
+ projectRoot: parsed.projectRoot,
131
+ filename: parsed.filename,
132
+ snapshotId: parsed.snapshotId,
133
+ }
134
+ }
135
+ } catch {
136
+ return undefined
137
+ }
138
+
139
+ return undefined
140
+ }
141
+
142
+ export function normalizeProviderRequestBodyForCacheDebug(params: {
143
+ provider: string
144
+ body: unknown
145
+ }): SerializableValue {
146
+ const parsed = parseRequestBody(params.body)
147
+ const body = normalizeForJson(parsed)
148
+
149
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
150
+ return body
151
+ }
152
+
153
+ const record = body as SerializableRecord
154
+ const normalized: SerializableRecord = {}
155
+
156
+ for (const key of ['model', 'messages', 'tools', 'tool_choice', 'response_format', 'reasoning', 'reasoning_effort', 'verbosity', 'provider']) {
157
+ if (key in record) {
158
+ normalized[key] = summarizeLargeValue(record[key])
159
+ }
160
+ }
161
+
162
+ if (params.provider === 'openrouter') {
163
+ for (const key of ['models', 'plugins', 'web_search_options', 'include_reasoning']) {
164
+ if (key in record) {
165
+ normalized[key] = summarizeLargeValue(record[key])
166
+ }
167
+ }
168
+ }
169
+
170
+ return normalized
171
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Normalize an HTTP `User-Agent` into a bounded token for analytics.
3
+ *
4
+ * Which client made a request is otherwise unanswerable server-side. The only
5
+ * client-identifying field we store is `session.fingerprint_id`, which the
6
+ * client picks for itself — reading it as client identity produced a
7
+ * 659-account false-positive ban on 2026-08-03. The request user agent is a
8
+ * second, independent signal: the official CLI sends `Anvil-CLI/<version>`
9
+ * (`getCliAdRequestUserAgent` in cli/src/hooks/use-gravity-ad.ts), so anything
10
+ * else is at least worth a look.
11
+ *
12
+ * It is **not** proof either: a user agent is equally client-supplied, and
13
+ * published Anvil proxies already spoof ours. Treat the output as a lead.
14
+ *
15
+ * Two properties make this safe to attach to a high-volume event:
16
+ *
17
+ * - **PII.** Only the leading RFC 7231 product token is kept, so every
18
+ * parenthetical detail is dropped before anything is recorded:
19
+ * `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 …`
20
+ * becomes `mozilla/5.0`. Nothing after the first whitespace is read.
21
+ * - **Cardinality.** Values are bounded in size and charset: the product is
22
+ * lowercased and stripped to `[a-z0-9._-]`, and anything empty or longer than
23
+ * 32 chars collapses to the single constant `unrecognized`. A version must
24
+ * both look like a version and fit in 16 chars or it is omitted. Nothing is
25
+ * ever truncated into a new value.
26
+ *
27
+ * This bounds each value, not the number of distinct values: a hostile client
28
+ * can still vary the product within 32 legal chars. That residual risk is
29
+ * accepted because an allowlist would defeat the entire point — spotting a
30
+ * client we have not seen before. It is why this field feeds analysis only,
31
+ * never enforcement, and why the chat-completions call site rides the
32
+ * existing 1% free-mode sample. If it is ever abused, the fix is an allowlist
33
+ * here, not at the call sites.
34
+ */
35
+
36
+ const MAX_PRODUCT_LEN = 32
37
+ const MAX_VERSION_LEN = 16
38
+
39
+ /** Junk, or a header we could not parse into a product token. */
40
+ export const UNRECOGNIZED_CLIENT = 'unrecognized'
41
+
42
+ /** A version-ish token: starts with a digit, then dotted/alphanumeric parts. */
43
+ const VERSION_RE = /^\d[0-9A-Za-z.+-]*$/
44
+
45
+ export type NormalizedClientUserAgent = {
46
+ /** Lowercased product token, e.g. `anvil-cli`, or `unrecognized`. */
47
+ product: string
48
+ /** Version token when the product declared a parseable one. */
49
+ version?: string
50
+ }
51
+
52
+ /**
53
+ * Returns `undefined` for an absent/empty header so callers can spread the
54
+ * result into an event payload without emitting empty keys.
55
+ */
56
+ export function normalizeClientUserAgent(
57
+ raw: string | null | undefined,
58
+ ): NormalizedClientUserAgent | undefined {
59
+ if (typeof raw !== 'string') return undefined
60
+ const trimmed = raw.trim()
61
+ if (!trimmed) return undefined
62
+
63
+ // RFC 7231: `product/version (comment) product/version …`. Everything from
64
+ // the first whitespace on is commentary — drop it unread.
65
+ const [firstToken = ''] = trimmed.split(/\s+/, 1)
66
+ const slash = firstToken.indexOf('/')
67
+ const rawProduct = slash === -1 ? firstToken : firstToken.slice(0, slash)
68
+ const rawVersion = slash === -1 ? '' : firstToken.slice(slash + 1)
69
+
70
+ // An `@` never appears in a legitimate product token, and stripping it would
71
+ // turn `user@example.com/1.0` into the recorded value `userexample.com` — a
72
+ // mangled but still-identifying address. Reject instead of sanitizing. (A
73
+ // contact address in the conventional `Bot/1.0 (+me@example.com)` position is
74
+ // already dropped: it is after the first whitespace.)
75
+ if (rawProduct.includes('@')) return { product: UNRECOGNIZED_CLIENT }
76
+
77
+ const product = rawProduct.toLowerCase().replace(/[^a-z0-9._-]/g, '')
78
+
79
+ // Empty after sanitizing, or implausibly long: collapse to one constant
80
+ // rather than record a truncated fabrication. Real product tokens are short
81
+ // (`anvil-cli` 12, `go-http-client` 14, `ai-sdk` 6), so anything past the
82
+ // cap is junk — and collapsing it also removes the cheapest way to pump
83
+ // cardinality, which is a long random string per request.
84
+ if (!product || product.length > MAX_PRODUCT_LEN) {
85
+ return { product: UNRECOGNIZED_CLIENT }
86
+ }
87
+
88
+ // Drop an over-long version rather than truncating it. Truncation fabricates
89
+ // a value the client never sent — `1.0.0-beta+build.1` would be reported as
90
+ // `1.0.0-beta+build`, which is both wrong and collides with `…build.2`. A
91
+ // missing version is honest; an invented one would quietly corrupt exactly
92
+ // the "is this client pinned to a stale version?" question this exists for.
93
+ if (rawVersion.length > MAX_VERSION_LEN || !VERSION_RE.test(rawVersion)) {
94
+ return { product }
95
+ }
96
+ return { product, version: rawVersion }
97
+ }
98
+
99
+ /**
100
+ * Event-payload shape for the normalized agent. Separate fields (rather than
101
+ * one joined string) keep "which clients exist" and "which versions of one
102
+ * client" independently groupable in APL.
103
+ */
104
+ export function clientUserAgentFields(raw: string | null | undefined): {
105
+ client_ua_product?: string
106
+ client_ua_version?: string
107
+ } {
108
+ const normalized = normalizeClientUserAgent(raw)
109
+ if (!normalized) return {}
110
+ return {
111
+ client_ua_product: normalized.product,
112
+ ...(normalized.version ? { client_ua_version: normalized.version } : {}),
113
+ }
114
+ }
@@ -0,0 +1,26 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ import { z } from 'zod/v4'
4
+
5
+ export const userSchema = z.object({
6
+ id: z.string(),
7
+ email: z.string(),
8
+ name: z.string().nullable(),
9
+ authToken: z.string(),
10
+ fingerprintId: z.string(),
11
+ fingerprintHash: z.string(),
12
+ })
13
+
14
+ export type User = z.infer<typeof userSchema>
15
+
16
+ export const genAuthCode = (
17
+ fingerprintId: string,
18
+ expiresAt: string,
19
+ secret: string,
20
+ ) =>
21
+ crypto
22
+ .createHash('sha256')
23
+ .update(secret)
24
+ .update(fingerprintId)
25
+ .update(expiresAt)
26
+ .digest('hex')
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Converts a credit amount to USD cents.
3
+ * @param credits The number of credits to convert
4
+ * @param centsPerCredit The cost per credit in cents
5
+ * @returns The amount in USD cents
6
+ */
7
+ export function convertCreditsToUsdCents(
8
+ credits: number,
9
+ centsPerCredit: number,
10
+ ): number {
11
+ return Math.ceil(credits * centsPerCredit)
12
+ }
13
+
14
+ /**
15
+ * Converts a Stripe grant amount in cents to credits.
16
+ * @param amountInCents The amount in USD cents
17
+ * @param centsPerCredit The cost per credit in cents
18
+ * @returns The number of credits
19
+ */
20
+ export function convertStripeGrantAmountToCredits(
21
+ amountInCents: number,
22
+ centsPerCredit: number,
23
+ ): number {
24
+ return Math.floor(amountInCents / centsPerCredit)
25
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Calculates the next quota reset date.
3
+ * If the current reset date is in the past or null, it calculates the next
4
+ * reset date based on the current date. Otherwise, it ensures the next
5
+ * reset date is in the future relative to the provided date.
6
+ *
7
+ * @param referenceDate The user's current `next_quota_reset` date, or the date the cycle ended.
8
+ * @returns The Date object representing the next reset time.
9
+ */
10
+ export const getNextQuotaReset = (referenceDate: Date | null): Date => {
11
+ const now = new Date()
12
+ let nextMonth = new Date(referenceDate ?? now)
13
+ while (nextMonth <= now) {
14
+ nextMonth.setMonth(nextMonth.getMonth() + 1)
15
+ }
16
+ return nextMonth
17
+ }
18
+
19
+ export interface FormatTimeUntilOptions {
20
+ /**
21
+ * What to return when the date is in the past or invalid.
22
+ * @default 'now'
23
+ */
24
+ fallback?: string
25
+ /**
26
+ * Whether to include the smaller unit (hours in "Xd Yh", minutes in "Xh Ym").
27
+ * @default true
28
+ */
29
+ includeSubUnit?: boolean
30
+ }
31
+
32
+ /**
33
+ * Format the time until a future date in a human-readable string.
34
+ *
35
+ * @param date - The target date (Date object or ISO string)
36
+ * @param options - Formatting options
37
+ * @returns Human-readable string like "4d 7h", "2h 30m", or "45m"
38
+ *
39
+ * @example
40
+ * // Date 2 days and 5 hours in the future
41
+ * formatTimeUntil(futureDate) // "2d 5h"
42
+ * formatTimeUntil(futureDate, { includeSubUnit: false }) // "2d"
43
+ *
44
+ * // Date 3 hours and 20 minutes in the future
45
+ * formatTimeUntil(futureDate) // "3h 20m"
46
+ *
47
+ * // Date in the past
48
+ * formatTimeUntil(pastDate) // "now"
49
+ * formatTimeUntil(pastDate, { fallback: '0h' }) // "0h"
50
+ */
51
+ export const formatTimeUntil = (
52
+ date: Date | string | null,
53
+ options: FormatTimeUntilOptions = {},
54
+ ): string => {
55
+ const { fallback = 'now', includeSubUnit = true } = options
56
+
57
+ if (!date) return fallback
58
+
59
+ const target = typeof date === 'string' ? new Date(date) : date
60
+ const diffMs = target.getTime() - Date.now()
61
+
62
+ if (isNaN(diffMs) || diffMs <= 0) return fallback
63
+
64
+ const diffMins = Math.floor(diffMs / (1000 * 60))
65
+ const diffHours = Math.floor(diffMins / 60)
66
+ const diffDays = Math.floor(diffHours / 24)
67
+ const remainingHours = diffHours % 24
68
+ const remainingMins = diffMins % 60
69
+
70
+ if (diffDays > 0) {
71
+ return includeSubUnit && remainingHours > 0
72
+ ? `${diffDays}d ${remainingHours}h`
73
+ : `${diffDays}d`
74
+ }
75
+ if (diffHours > 0) {
76
+ return includeSubUnit && remainingMins > 0
77
+ ? `${diffHours}h ${remainingMins}m`
78
+ : `${diffHours}h`
79
+ }
80
+ return `${diffMins}m`
81
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Classify email domains that referral-abuse detection treats as flags.
3
+ *
4
+ * Two categories, kept separate because they carry different weight:
5
+ *
6
+ * - `disposable` — one-time / throwaway inbox providers. A real person has no
7
+ * reason to sign up for a durable dev-tool account with an inbox that stops
8
+ * existing in an hour, so a *referred* account on one of these is a strong
9
+ * farm signal.
10
+ * - `privacy_relay` — burner-friendly privacy providers and relays
11
+ * (Proton, Apple private relay, Firefox Relay, SimpleLogin, …). Plenty of
12
+ * legitimate developers live on these, so a hit is corroborating evidence
13
+ * only — it must never gate a reward or trigger action on its own.
14
+ *
15
+ * Matching is by exact domain or any subdomain (disposable providers hand out
16
+ * wildcard subdomains). Lists are deliberately curated and small: they exist
17
+ * to catch the providers we actually see in referral farms, not to be a
18
+ * complete registry. Extend them as sweeps surface new ones (note the dated
19
+ * "observed in referral farms" block below).
20
+ */
21
+
22
+ export type FlaggedEmailDomainKind =
23
+ | 'disposable'
24
+ | 'privacy_relay'
25
+ /** A big-brand consumer privacy mailbox (Proton, Tutanota, Apple
26
+ * Hide-My-Email, DuckDuckGo, Firefox Relay). Classified so callers can SEE
27
+ * it, but the spend ceiling deliberately does not price it — see
28
+ * isSpendCeilingFlaggedEmailDomain. */
29
+ | 'mainstream_privacy'
30
+
31
+ const DISPOSABLE_EMAIL_DOMAINS = [
32
+ // Classic one-time inbox providers.
33
+ '10minutemail.com',
34
+ 'dispostable.com',
35
+ 'dropmail.me',
36
+ 'emailondeck.com',
37
+ 'fakeinbox.com',
38
+ 'getnada.com',
39
+ 'grr.la',
40
+ 'guerrillamail.com',
41
+ 'guerrillamail.net',
42
+ 'maildrop.cc',
43
+ 'mailinator.com',
44
+ 'mailnesia.com',
45
+ 'mail.tm',
46
+ 'minuteinbox.com',
47
+ 'mintemail.com',
48
+ 'mohmal.com',
49
+ 'sharklasers.com',
50
+ 'temp-mail.org',
51
+ 'tempinbox.com',
52
+ 'tempmail.com',
53
+ 'tempmail.dev',
54
+ 'throwawaymail.com',
55
+ 'trashmail.com',
56
+ 'yopmail.com',
57
+ // Observed in Anvil referral farms, 2026-07 (scripted rings minting
58
+ // referred accounts on niche throwaway domains — see the 07-29 sock sweep).
59
+ 'aifotoeditor.com',
60
+ 'animateany.com',
61
+ 'animatimg.com',
62
+ 'biscoito.email',
63
+ 'oldtranslator.com',
64
+ // Observed 2026-08-01: a 48-account free-mode compute ring, all minted on
65
+ // 12-character random local parts and admitted from one egress IP (peak 30
66
+ // concurrent, four admissions inside two seconds). These are catch-all
67
+ // domains whose subdomains are chosen to read as legitimate at a glance —
68
+ // gmail.l0veyou.com, edu.l0veyou.com, my.l0veyou.com, test123.l0veyou.com,
69
+ // gmail.pumpkinai.space — which subdomain matching above already covers.
70
+ 'l0veyou.com',
71
+ 'pumpkinai.space',
72
+ 'pumpkinai.it.com',
73
+ // Compiled 2026-08-03 from prod, not from a public blocklist: every domain
74
+ // below has >=20 Anvil accounts and >=75% of them already banned. That
75
+ // covers 7,129 accounts of which 6,699 were banned before this list existed.
76
+ // The threshold is what keeps real providers out — Gmail sits at 0.8%
77
+ // banned, Outlook 11%, Proton 4%, duck.com 30%, and the two school domains
78
+ // that appeared in the raw data (dmpschool.ac.th 12%, sman1pace.sch.id, 5
79
+ // accounts) fail the volume or rate bar. Many are deliberate Gmail
80
+ // lookalikes (gmaiko, gmbel, ggmul, gkmaill, gmito, gmiliu, gmisol) and
81
+ // several were minted in a single day — gmito.my.id put 288 accounts on one
82
+ // date, which no real mailbox provider does.
83
+ 'azahram.com',
84
+ 'barcondi.my.id',
85
+ 'bukitsakura.com',
86
+ 'cilisung.com',
87
+ 'cindohub.com',
88
+ 'duccky.com',
89
+ 'fomolu.com',
90
+ 'gamlo.my.id',
91
+ 'gamontok.com',
92
+ 'gehil.my.id',
93
+ 'geusil.com',
94
+ 'geusil.my.id',
95
+ 'ggmul.com',
96
+ 'ghyuil.my.id',
97
+ 'gkmaill.com',
98
+ 'gmaiko.com',
99
+ 'gmbel.com',
100
+ 'gmiliu.my.id',
101
+ 'gmisol.my.id',
102
+ 'gmito.my.id',
103
+ 'gmole.xyz',
104
+ 'gmosel.com',
105
+ 'gsuel.my.id',
106
+ 'gumel.store',
107
+ 'guzeil.com',
108
+ 'gwemol.my.id',
109
+ 'hayate.us',
110
+ 'jokowi.store',
111
+ 'jujusa.my.id',
112
+ 'mikontol.online',
113
+ 'monetsssky1.com',
114
+ 'satukataku.com',
115
+ 'simosel.site',
116
+ 'wdrvk.dpdns.org',
117
+ 'wdrvks.eu.org',
118
+ 'xabree.com',
119
+ // Added 2026-08-03 on behavioural rather than statistical evidence — these
120
+ // are below the >=20-account bar the block above uses, so they qualify on
121
+ // what their accounts DO instead.
122
+ //
123
+ // proxyvpn.cn: 5 accounts named github, github-1/-2/-3 and master-github,
124
+ // on a proxy-service domain. All five were silent-endpoint callers (free-mode
125
+ // messages with zero client telemetry) running 372-1,435 messages each in
126
+ // 7 days; all five are now banned.
127
+ //
128
+ // The .qd.je pair: 14 accounts each, every one registered on a SINGLE day
129
+ // (impact 2026-07-10, fincy 2026-07-13), and 13 of the 28 surfaced in the
130
+ // silent-endpoint scan. A same-day mint of 14 accounts is not a mailbox
131
+ // provider signing up users.
132
+ //
133
+ // Deliberately NOT added: dns-proxy.com (1 account, no activity, no
134
+ // evidence) and proximus.lu (Belgium's largest telecom — a real ISP whose
135
+ // one user has 791 legitimate messages). A substring rule on "proxy" would
136
+ // have caught both.
137
+ 'proxyvpn.cn',
138
+ 'impact.qd.je',
139
+ 'fincy.qd.je',
140
+ // Compiled 2026-08-15 from 14d of prod logs, while working out why 22.9% of
141
+ // Singapore's active users were hitting a spend ceiling. The restricted
142
+ // ceiling on the SG *geography* was doing two jobs at once: bounding a farm
143
+ // that happens to egress there, and taxing ~270 ordinary developers on
144
+ // gmail/qq/163/outlook. Naming the farm by domain is what lets the geography
145
+ // cap come off — see ANVIL_ELEVATED_COUNTRIES.
146
+ //
147
+ // Rates below are "share of the domain's accounts carrying an automated ban,
148
+ // honeypot hit, or foreign-toolset detection". Baselines from the same
149
+ // window: gmail.com 5%, proton.me 7%, outlook.com 23%.
150
+ //
151
+ // dhisy.com 28 accounts, 93% — clears the >=20 / >=75% statistical bar
152
+ // dewaa.id 20 accounts, 80% — clears it
153
+ // sendang.space 13 accounts, 92% — behavioural: too few accounts for the
154
+ // bar, ban rate far past it
155
+ // yotube.id 18 accounts, 83% — behavioural, and a YouTube typo-squat
156
+ // gusil.my.id 80 accounts, 68% — under 75%, but 13x the gmail baseline
157
+ // at real volume, and the same naming
158
+ // family as gsuel/gehil/gmisol.my.id
159
+ // already listed above
160
+ //
161
+ // Deliberately NOT added, and worth recording because both LOOK like members
162
+ // of the family above and are not:
163
+ // gmisel.com 49 accounts, 2% — indistinguishable from gmail's baseline
164
+ // despite sitting one letter from the
165
+ // listed gmisol.my.id. Name similarity is
166
+ // not evidence; this is the entry the
167
+ // >=75% bar exists to keep out.
168
+ // cemararaya.id 6 accounts, 33% — too few accounts, rate too low.
169
+ 'dhisy.com',
170
+ 'dewaa.id',
171
+ 'sendang.space',
172
+ 'yotube.id',
173
+ 'gusil.my.id',
174
+ ] as const
175
+
176
+ /**
177
+ * Big-brand consumer privacy mailboxes, lifted OFF the spend ceiling on an
178
+ * operator decision (2026-08-14).
179
+ *
180
+ * These were in the relay list from day one, which put a bot-farm ceiling on
181
+ * every ProtonMail and Tutanota user — mainstream providers whose typical
182
+ * account is one person's primary mailbox, not an alias mint. It also
183
+ * contradicted the pipeline's own doctrine: the IP side refuses to price
184
+ * Apple Private Relay because it is a consumer default, while the email side
185
+ * was pricing Apple's Hide-My-Email. Alias services built for
186
+ * one-address-per-signup (SimpleLogin, AnonAddy, aleeas) stay priced: their
187
+ * product IS mailbox multiplication, which is the thing the flag exists to
188
+ * catch.
189
+ *
190
+ * Still classified — as `mainstream_privacy` — so trust scoring and the
191
+ * abuse scripts can keep seeing them; only the ceiling looks away.
192
+ */
193
+ const MAINSTREAM_PRIVACY_EMAIL_DOMAINS = [
194
+ // Proton family (primary mailboxes; passmail.net is Proton's ALIAS product
195
+ // and stays in the relay list below).
196
+ 'proton.me',
197
+ 'protonmail.ch',
198
+ 'protonmail.com',
199
+ 'pm.me',
200
+ // Apple "Hide My Email".
201
+ 'privaterelay.appleid.com',
202
+ // DuckDuckGo Email Protection.
203
+ 'duck.com',
204
+ // Firefox Relay.
205
+ 'mozmail.com',
206
+ // Tutanota family.
207
+ 'tuta.com',
208
+ 'tuta.io',
209
+ 'tutamail.com',
210
+ 'tutanota.com',
211
+ ] as const
212
+
213
+ const PRIVACY_RELAY_EMAIL_DOMAINS = [
214
+ 'passmail.net',
215
+ // Alias/relay services.
216
+ 'aleeas.com',
217
+ 'anonaddy.me',
218
+ 'simplelogin.com',
219
+ 'simplelogin.io',
220
+ ] as const
221
+
222
+ const DISPOSABLE_SET: ReadonlySet<string> = new Set(DISPOSABLE_EMAIL_DOMAINS)
223
+ const PRIVACY_RELAY_SET: ReadonlySet<string> = new Set(
224
+ PRIVACY_RELAY_EMAIL_DOMAINS,
225
+ )
226
+ const MAINSTREAM_PRIVACY_SET: ReadonlySet<string> = new Set(
227
+ MAINSTREAM_PRIVACY_EMAIL_DOMAINS,
228
+ )
229
+
230
+ function domainOf(email: string): string | null {
231
+ const at = email.lastIndexOf('@')
232
+ if (at < 0 || at === email.length - 1) return null
233
+ return email
234
+ .slice(at + 1)
235
+ .trim()
236
+ .toLowerCase()
237
+ }
238
+
239
+ function matchesSet(domain: string, set: ReadonlySet<string>): boolean {
240
+ if (set.has(domain)) return true
241
+ // Subdomain match: a.b.mailinator.com → b.mailinator.com → mailinator.com
242
+ let rest = domain
243
+ for (let dot = rest.indexOf('.'); dot >= 0; dot = rest.indexOf('.')) {
244
+ rest = rest.slice(dot + 1)
245
+ if (set.has(rest)) return true
246
+ }
247
+ return false
248
+ }
249
+
250
+ /** The flag category for `email`'s domain, or null for an ordinary domain
251
+ * (or an unparseable email). */
252
+ export function classifyEmailDomain(
253
+ email: string | null | undefined,
254
+ ): FlaggedEmailDomainKind | null {
255
+ if (!email) return null
256
+ const domain = domainOf(email)
257
+ if (!domain) return null
258
+ if (matchesSet(domain, DISPOSABLE_SET)) return 'disposable'
259
+ if (matchesSet(domain, PRIVACY_RELAY_SET)) return 'privacy_relay'
260
+ if (matchesSet(domain, MAINSTREAM_PRIVACY_SET)) return 'mainstream_privacy'
261
+ return null
262
+ }
263
+
264
+ /**
265
+ * Whether `email`'s domain prices the flagged-domain SPEND CEILING.
266
+ *
267
+ * Deliberately narrower than `classifyEmailDomain !== null`: mainstream
268
+ * consumer privacy mailboxes are classified (so scoring and scripts can see
269
+ * them) but never priced — a ProtonMail address is not evidence of a bot
270
+ * farm, and for two days it was billed as one.
271
+ */
272
+ export function isSpendCeilingFlaggedEmailDomain(
273
+ email: string | null | undefined,
274
+ ): boolean {
275
+ const kind = classifyEmailDomain(email)
276
+ return kind !== null && kind !== 'mainstream_privacy'
277
+ }