@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,55 @@
1
+ /**
2
+ * Who gets shown the onboarding form.
3
+ *
4
+ * Nothing here is a permission check. Onboarding never refuses a request on any
5
+ * surface — the entire mechanism is one redirect, to a form with a Skip button,
6
+ * fired at most once per browser. What this decides is who is interrupted.
7
+ *
8
+ * The answer is: **anyone who has not answered, once.** New signups get it on
9
+ * their way in; a long-standing account gets it the next time they sign in
10
+ * through the web portal. There is deliberately no cutover date any more. One
11
+ * existed when the form was a blocking gate, to keep existing users from being
12
+ * locked out of a product they already used — with nothing to lock them out of,
13
+ * a cutover only means never learning anything about the 200,000 accounts we
14
+ * already have. The Skip button and the seen-cookie are what protect them now,
15
+ * and they cost a single dismissible screen instead of an exemption.
16
+ *
17
+ * The polarity is deliberate: **an unset or `off` switch shows it to NOBODY.**
18
+ * Absence of config disables the feature rather than enabling it universally.
19
+ * This repo has already paid for the opposite convention once — a deleted env
20
+ * var silently activating a default is documented in
21
+ * db-capacity-and-scaling.md §10.6 — and here that mistake would present as
22
+ * every user meeting a form nobody decided to launch.
23
+ */
24
+
25
+ export type OnboardingRequirementInput = {
26
+ /** Parsed `ANVIL_ONBOARDING_ENABLED`. */
27
+ enabled: boolean
28
+ /** Whether the user has answered every question. */
29
+ complete: boolean
30
+ }
31
+
32
+ export type OnboardingRequirement =
33
+ | { required: false; reason: 'gate_disabled' | 'already_complete' }
34
+ | { required: true }
35
+
36
+ export function evaluateOnboardingRequirement(
37
+ input: OnboardingRequirementInput,
38
+ ): OnboardingRequirement {
39
+ // Not switched on — nobody is asked. See the polarity note above.
40
+ if (!input.enabled) return { required: false, reason: 'gate_disabled' }
41
+ if (input.complete) return { required: false, reason: 'already_complete' }
42
+ return { required: true }
43
+ }
44
+
45
+ /**
46
+ * Read the on/off switch.
47
+ *
48
+ * Anything other than an explicit affirmative is off, so a typo, an empty
49
+ * string or a deleted variable all fail the same safe way.
50
+ */
51
+ export function parseOnboardingEnabled(raw: string | null | undefined): boolean {
52
+ const trimmed = raw?.trim().toLowerCase()
53
+ return trimmed === 'on' || trimmed === 'true' || trimmed === '1'
54
+ }
55
+
@@ -0,0 +1,307 @@
1
+ /**
2
+ * The post-signup onboarding questionnaire.
3
+ *
4
+ * Five questions, each a fixed option list plus a free-text "Other". Shared
5
+ * between the form, the persistence layer and the admin view so a renamed
6
+ * option cannot silently split one answer into two in the analytics.
7
+ *
8
+ * Why the option ids are opaque and permanent
9
+ * -------------------------------------------
10
+ * The stored value is the id, never the label. Labels are copy and will be
11
+ * reworded; ids are data. Changing a label is free, changing an id
12
+ * retroactively rewrites history — so ids here are append-only. Retire an
13
+ * option by removing it from the list, never by renaming it.
14
+ *
15
+ * When an option is retired or broadened, its old answers do not disappear:
16
+ * `ONBOARDING_LEGACY_OPTION_IDS` below maps the retired id onto whichever
17
+ * current option now covers it, and the admin tally applies that at read time.
18
+ * Nothing rewrites what is stored — the stored answer stays exactly what the
19
+ * person picked on the day they picked it.
20
+ */
21
+
22
+ export type OnboardingQuestionId =
23
+ | 'referral_source'
24
+ | 'role'
25
+ | 'proficiency'
26
+ | 'intended_use'
27
+ | 'subscriptions'
28
+
29
+ export type OnboardingOption = {
30
+ /** Stored verbatim. Append-only — see the note above. */
31
+ id: string
32
+ label: string
33
+ /** Choosing this clears every other option, and vice versa. Only meaningful
34
+ * on a multi-select — "none of them" and "these ones" cannot both be true. */
35
+ exclusive?: boolean
36
+ }
37
+
38
+ export type OnboardingQuestion = {
39
+ id: OnboardingQuestionId
40
+ prompt: string
41
+ options: OnboardingOption[]
42
+ /** Whether more than one option may be chosen. */
43
+ multi: boolean
44
+ /** `scale` renders as a slider: the options are an ordered ramp, so the
45
+ * distance between two answers means something and the control should say
46
+ * so. Everything else is a chip list. Storage is identical either way. */
47
+ kind?: 'choice' | 'scale'
48
+ }
49
+
50
+ /** Every question offers this. The accompanying free text is stored separately
51
+ * so "other" stays countable while the text stays readable. */
52
+ export const OTHER_OPTION_ID = 'other'
53
+
54
+ export const ANVIL_ONBOARDING_QUESTIONS: readonly OnboardingQuestion[] = [
55
+ {
56
+ id: 'referral_source',
57
+ prompt: 'Where did you hear about Anvil?',
58
+ // Attribution is the one answer we cannot reconstruct later: referrer
59
+ // headers are stripped, and self-report is the only signal for word of
60
+ // mouth, which is where most of it actually comes from.
61
+ //
62
+ // Instagram leads because it was the single largest write-in by a distance,
63
+ // and it shares an audience with TikTok closely enough that splitting them
64
+ // would buy two thin numbers instead of one usable one.
65
+ options: [
66
+ // Keeps the `tiktok` id: those answers are the same audience, and a new
67
+ // id would restart the count from zero for the sake of a tidier string.
68
+ { id: 'tiktok', label: 'Instagram / TikTok' },
69
+ { id: 'youtube', label: 'YouTube' },
70
+ { id: 'x_twitter', label: 'X / Twitter' },
71
+ { id: 'search', label: 'Google / AI search' },
72
+ { id: 'friend', label: 'A friend' },
73
+ { id: 'reddit', label: 'Reddit' },
74
+ { id: 'github', label: 'GitHub' },
75
+ { id: OTHER_OPTION_ID, label: 'Somewhere else' },
76
+ ],
77
+ multi: false,
78
+ },
79
+ {
80
+ id: 'role',
81
+ prompt: 'What best describes you?',
82
+ options: [
83
+ { id: 'professional_dev', label: 'Developer' },
84
+ { id: 'founder', label: 'Founder' },
85
+ { id: 'student', label: 'Student' },
86
+ { id: 'hobbyist', label: 'Hobbyist' },
87
+ { id: 'pm', label: 'Designer or PM' },
88
+ { id: 'non_technical', label: 'Non-technical' },
89
+ { id: OTHER_OPTION_ID, label: 'Something else' },
90
+ ],
91
+ multi: false,
92
+ },
93
+ {
94
+ id: 'proficiency',
95
+ prompt: 'How much do you code?',
96
+ options: [
97
+ { id: 'none', label: 'Not at all' },
98
+ { id: 'beginner', label: 'Beginner' },
99
+ { id: 'intermediate', label: 'Intermediate' },
100
+ { id: 'advanced', label: 'Advanced' },
101
+ { id: 'expert', label: 'Expert' },
102
+ ],
103
+ // No Other: this is an ordered scale, and a free-text answer on an ordinal
104
+ // question is unusable for the segmentation it exists to support.
105
+ multi: false,
106
+ kind: 'scale',
107
+ },
108
+ {
109
+ id: 'intended_use',
110
+ prompt: 'What will you build with Anvil?',
111
+ options: [
112
+ { id: 'website', label: 'Websites and apps' },
113
+ { id: 'work', label: 'Work projects' },
114
+ { id: 'side_projects', label: 'Side projects' },
115
+ { id: 'learning', label: 'Learning to code' },
116
+ { id: 'automation', label: 'Scripts and automation' },
117
+ { id: OTHER_OPTION_ID, label: 'Something else' },
118
+ ],
119
+ multi: true,
120
+ },
121
+ {
122
+ id: 'subscriptions',
123
+ prompt: 'Current subscriptions that you’ve had, cancelled or will cancel',
124
+ // The one question here that prices the product rather than describing the
125
+ // user: what someone already pays for — and is willing to stop paying for —
126
+ // says who we actually compete with. Deliberately loose about tense, since
127
+ // "about to cancel" is the same competitive signal as "already did" and
128
+ // splitting the two would buy two thin numbers instead of one usable one.
129
+ options: [
130
+ { id: 'none', label: 'None', exclusive: true },
131
+ { id: 'claude_code', label: 'Claude Code' },
132
+ { id: 'cursor', label: 'Cursor' },
133
+ { id: 'opencode', label: 'opencode' },
134
+ { id: 'copilot', label: 'GitHub Copilot' },
135
+ { id: 'codex', label: 'Codex' },
136
+ { id: 'gemini', label: 'Gemini' },
137
+ { id: 'app_builder', label: 'Lovable / Replit' },
138
+ { id: OTHER_OPTION_ID, label: 'Something else' },
139
+ ],
140
+ multi: true,
141
+ },
142
+ ] as const
143
+
144
+ /**
145
+ * Retired option id → the current option that now covers it.
146
+ *
147
+ * Read-time only, applied by the admin tally. A retired option's answers are
148
+ * still real answers; folding them into their successor keeps a broadened
149
+ * option's count honest instead of resetting it to zero on the day of the edit.
150
+ * An id with no successor here is simply not counted.
151
+ */
152
+ export const ONBOARDING_LEGACY_OPTION_IDS: Partial<
153
+ Record<OnboardingQuestionId, Record<string, string>>
154
+ > = {
155
+ // Discord and blog/newsletter were dropped as options; both are honestly
156
+ // "somewhere else" rather than any of the channels that remain.
157
+ referral_source: { discord: OTHER_OPTION_ID, blog_news: OTHER_OPTION_ID },
158
+ // Data/ML folded into the broader developer bucket, designer into the
159
+ // designer-or-PM one.
160
+ role: { data_ml: 'professional_dev', designer: 'pm' },
161
+ // Prototypes and demos are websites and apps; code review has no successor
162
+ // narrow enough to claim it, so it goes to "something else".
163
+ intended_use: { prototyping: 'website', debugging: OTHER_OPTION_ID },
164
+ }
165
+
166
+ /**
167
+ * Write-in text → the option it should have been.
168
+ *
169
+ * People type an answer that already exists on the list, at volume: Instagram
170
+ * was the top write-in on a question that did not offer it, and every variant
171
+ * of "ChatGPT" is the AI-search answer. Matching is read-time and deliberately
172
+ * narrow — a pattern that fires on an ambiguous word buys a wrong count, which
173
+ * is worse than the "other" bucket it came from.
174
+ */
175
+ export const ONBOARDING_OTHER_TEXT_RULES: Partial<
176
+ Record<OnboardingQuestionId, { optionId: string; pattern: RegExp }[]>
177
+ > = {
178
+ referral_source: [
179
+ { optionId: 'tiktok', pattern: /insta|\btiktok\b|\btik tok\b|\big\b/i },
180
+ {
181
+ optionId: 'search',
182
+ pattern:
183
+ /\bai\b|chat\s?gpt|\bgpt\b|claude|gemini|perplexity|copilot|grok|deepseek/i,
184
+ },
185
+ ],
186
+ }
187
+
188
+ /**
189
+ * Which option a write-in should be counted as, if any.
190
+ *
191
+ * Rules are tried in order and the first match wins, so the more specific
192
+ * pattern must come first — "insta" before the AI catch-all, since "instagram
193
+ * AI" is an Instagram answer.
194
+ */
195
+ export function classifyOnboardingOtherText(
196
+ questionId: OnboardingQuestionId,
197
+ text: string,
198
+ ): string | null {
199
+ const rules = ONBOARDING_OTHER_TEXT_RULES[questionId]
200
+ if (!rules) return null
201
+ for (const rule of rules) {
202
+ if (rule.pattern.test(text)) return rule.optionId
203
+ }
204
+ return null
205
+ }
206
+
207
+ export type OnboardingAnswer = {
208
+ questionId: OnboardingQuestionId
209
+ /** Chosen option ids. Single-choice questions carry exactly one. */
210
+ optionIds: string[]
211
+ /** Free text, only when `other` is among the chosen ids. */
212
+ otherText?: string
213
+ }
214
+
215
+ export type OnboardingSubmission = {
216
+ answers: OnboardingAnswer[]
217
+ }
218
+
219
+ export const ONBOARDING_OTHER_TEXT_MAX = 200
220
+
221
+ export type OnboardingValidationError = {
222
+ /** Null when the complaint is about the submission as a whole rather than
223
+ * about one question — the only such case is an entirely empty one. */
224
+ questionId: OnboardingQuestionId | null
225
+ message: string
226
+ }
227
+
228
+ /**
229
+ * Validate a submission against the question set.
230
+ *
231
+ * Deliberately strict about ids and lenient about text: an unknown option id
232
+ * means the client is out of date or forged, and accepting it would put a value
233
+ * in the analytics that no question can explain. Free text is merely trimmed
234
+ * and truncated — it is never interpreted, only displayed.
235
+ */
236
+ export function validateOnboardingSubmission(
237
+ submission: OnboardingSubmission,
238
+ questions: readonly OnboardingQuestion[] = ANVIL_ONBOARDING_QUESTIONS,
239
+ ): { ok: true; answers: OnboardingAnswer[] } | { ok: false; errors: OnboardingValidationError[] } {
240
+ const errors: OnboardingValidationError[] = []
241
+ const cleaned: OnboardingAnswer[] = []
242
+
243
+ for (const question of questions) {
244
+ const answer = submission.answers.find((a) => a.questionId === question.id)
245
+ const optionIds = answer?.optionIds ?? []
246
+
247
+ if (optionIds.length === 0) {
248
+ errors.push({ questionId: question.id, message: 'Please choose an option.' })
249
+ continue
250
+ }
251
+ if (!question.multi && optionIds.length > 1) {
252
+ errors.push({ questionId: question.id, message: 'Choose one option.' })
253
+ continue
254
+ }
255
+
256
+ const valid = new Set(question.options.map((o) => o.id))
257
+ const unknown = optionIds.filter((id) => !valid.has(id))
258
+ if (unknown.length > 0) {
259
+ errors.push({ questionId: question.id, message: 'Unrecognised option.' })
260
+ continue
261
+ }
262
+
263
+ const exclusive = question.options.filter((o) => o.exclusive).map((o) => o.id)
264
+ if (optionIds.length > 1 && optionIds.some((id) => exclusive.includes(id))) {
265
+ errors.push({
266
+ questionId: question.id,
267
+ message: 'That answer cannot be combined with the others.',
268
+ })
269
+ continue
270
+ }
271
+
272
+ const wantsOther = optionIds.includes(OTHER_OPTION_ID)
273
+ const otherText = answer?.otherText?.trim()
274
+ if (wantsOther && !otherText) {
275
+ errors.push({
276
+ questionId: question.id,
277
+ message: 'Tell us a little more.',
278
+ })
279
+ continue
280
+ }
281
+
282
+ cleaned.push({
283
+ questionId: question.id,
284
+ optionIds,
285
+ // Dropped unless `other` was chosen, so stray text cannot ride along on
286
+ // an answer that has nowhere to show it.
287
+ ...(wantsOther && otherText
288
+ ? { otherText: otherText.slice(0, ONBOARDING_OTHER_TEXT_MAX) }
289
+ : {}),
290
+ })
291
+ }
292
+
293
+ return errors.length > 0 ? { ok: false, errors } : { ok: true, answers: cleaned }
294
+ }
295
+
296
+ /** Every question answered. Decides only whether someone who returns to the
297
+ * form still sees it — nothing is withheld either way. */
298
+ export function isOnboardingComplete(
299
+ answers: readonly OnboardingAnswer[] | null | undefined,
300
+ questions: readonly OnboardingQuestion[] = ANVIL_ONBOARDING_QUESTIONS,
301
+ ): boolean {
302
+ if (!answers || answers.length === 0) return false
303
+ const answered = new Set(
304
+ answers.filter((a) => a.optionIds.length > 0).map((a) => a.questionId),
305
+ )
306
+ return questions.every((q) => answered.has(q.id))
307
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Anvil referral program constants.
3
+ *
4
+ * The referral REWARD is consolidated (2026-07-30) to exactly two things,
5
+ * both session-shaped and both defined next to the pools they feed:
6
+ *
7
+ * - FULL access tier: +1 daily GLM 5.2 session per qualified referral,
8
+ * uncapped (glmWeeklySessionsFromStats in packages/billing).
9
+ * - LIMITED access tier: +1 daily free session per qualified referral,
10
+ * capped at REFERRAL_CLI_DAILY_SESSION_BONUS_CAP (GLM is geo-gated).
11
+ *
12
+ * The old web tier ladder (tiered daily message limits + deploy-watermark
13
+ * removal) is GONE: the limits were never enforced anywhere — real limits are
14
+ * the session pools in anvil-models.ts — and the "Powered by Anvil"
15
+ * deploy watermark is globally disabled (prod_branding_injection_enabled =
16
+ * false), so both perks were marketing for things users didn't get. What
17
+ * remains in this file is the qualification machinery: GitHub account-age
18
+ * bars, attribution windows, and anti-farming ceilings.
19
+ */
20
+
21
+ /** Referred users must have a GitHub account at least this old for the
22
+ * referral to count. Younger accounts can still sign up normally — the
23
+ * referrer just gets no credit (anti-farming). */
24
+ export const MIN_GITHUB_ACCOUNT_AGE_MONTHS = 4
25
+
26
+ /** GLM 5.2 referral program uses a stricter account-age bar than the web
27
+ * program (no public-repo requirement, but the account must be a full year
28
+ * old) since the reward — paid GLM serverless time — costs more to abuse. */
29
+ export const MIN_GITHUB_ACCOUNT_AGE_MONTHS_GLM = 12
30
+
31
+ /**
32
+ * Unified referral system (docs/referrals.md): a single 4-month GitHub
33
+ * account-age bar for ALL products (no public-repo requirement), consolidating
34
+ * the old per-program bars (cli/glm = 12mo, web = 4mo).
35
+ */
36
+ export const MIN_GITHUB_ACCOUNT_AGE_MONTHS_REFERRAL = 4
37
+
38
+ /**
39
+ * Anvil CLI limited-tier perk: each qualified referral whose referred user
40
+ * activated at the *limited* access tier grants +1 daily free-mode session,
41
+ * capped here (e.g. 6 base + 3 = 9/day). Full-access referrals instead grant
42
+ * GLM sessions, which are uncapped.
43
+ */
44
+ export const REFERRAL_CLI_DAILY_SESSION_BONUS_CAP = 3
45
+
46
+ /**
47
+ * Max attributed web signups (pending + completed) per referrer. The shared
48
+ * `user.referral_limit` column (default 5) governs the CLI program; the web
49
+ * program needs its own headroom — generous enough for unqualified signups,
50
+ * small enough to bound farming.
51
+ */
52
+ export const ANVIL_WEB_REFERRAL_LIMIT = 20
53
+
54
+ /**
55
+ * A referral can only be attributed within this many days of the referred
56
+ * user's signup — stops a referrer from claiming long-pre-existing accounts.
57
+ * Shared by the unified referral_v2 redemption path and its clients.
58
+ */
59
+ export const REFERRAL_SIGNUP_WINDOW_DAYS = 30
60
+
61
+ /**
62
+ * Max attributed referrals (referral_v2 rows) a single referrer may accumulate.
63
+ * Since the GLM reward was uncapped (2026-07-30) this is no longer a pure
64
+ * anti-spam ceiling: GLM entitlement now scales 1:1 with qualified referrals,
65
+ * so this limit IS the effective maximum (100 GLM sessions/day). The other
66
+ * rewards are still capped at read time (the web tier ladder, the CLI bonus at
67
+ * REFERRAL_CLI_DAILY_SESSION_BONUS_CAP), so for those it stays a formality.
68
+ */
69
+ export const ANVIL_REFERRAL_SIGNUP_LIMIT = 100
70
+
71
+ /** Whether a GitHub account created at `githubCreatedAtMs` satisfies the
72
+ * referral age requirement at time `nowMs`. Months are computed on the
73
+ * calendar (e.g. created Jan 15 qualifies on/after May 15). `minMonths`
74
+ * defaults to the web bar; the GLM program passes
75
+ * MIN_GITHUB_ACCOUNT_AGE_MONTHS_GLM. */
76
+ export function isGithubAccountOldEnoughForReferral(
77
+ githubCreatedAtMs: number | null | undefined,
78
+ nowMs: number = Date.now(),
79
+ minMonths: number = MIN_GITHUB_ACCOUNT_AGE_MONTHS,
80
+ ): boolean {
81
+ if (githubCreatedAtMs == null || !Number.isFinite(githubCreatedAtMs)) {
82
+ return false
83
+ }
84
+ const threshold = new Date(githubCreatedAtMs)
85
+ threshold.setUTCMonth(threshold.getUTCMonth() + minMonths)
86
+ return nowMs >= threshold.getTime()
87
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Signup-refusal reasons and their user-facing copy.
3
+ *
4
+ * Lives in `common`, not in `packages/auth`, because the login pages are client
5
+ * components: importing this from the auth barrel would drag `db`, `stripe` and
6
+ * the server env schema into the browser bundle. The decision logic stays in
7
+ * `@anvil/auth/signup-gate`; only the vocabulary is shared.
8
+ */
9
+
10
+ /** Why a signup was refused. Also the `?error=` code on the login redirect. */
11
+ export type SignupBlockReason =
12
+ | 'captcha_missing'
13
+ | 'captcha_invalid'
14
+ // Deliberately distinct from the two above rather than folded into them.
15
+ // The providers fail for different populations — a filter that blocks
16
+ // challenges.cloudflare.com usually does not block www.google.com, and vice
17
+ // versa — so a shared reason code would make the one question worth asking
18
+ // after a refusal spike ("which provider is failing?") unanswerable from the
19
+ // logs. That is exactly the ambiguity that cost four attempts on 2026-08-12.
20
+ | 'recaptcha_missing'
21
+ | 'recaptcha_invalid'
22
+ | 'mailbox_already_registered'
23
+ | 'privacy_egress'
24
+ | 'untrusted_client_ip'
25
+ | 'ip_signup_velocity'
26
+ | 'prefix_signup_velocity'
27
+
28
+ /**
29
+ * Deliberately actionable and non-accusatory.
30
+ *
31
+ * Someone hitting `privacy_egress` is far more likely to be a privacy-conscious
32
+ * developer on a VPN than a reseller, and the message they get should name the
33
+ * one thing that fixes it rather than implying they did something wrong. A
34
+ * refused signup with no explanation is indistinguishable from the product
35
+ * being broken, and generates a support ticket instead of a retry.
36
+ */
37
+ export const SIGNUP_BLOCK_MESSAGES: Record<SignupBlockReason, string> = {
38
+ captcha_missing:
39
+ 'Please complete the verification check on the sign-in page and try again.',
40
+ captcha_invalid:
41
+ 'That verification check could not be confirmed. Please try again.',
42
+ // Names the host, because the overwhelmingly likely cause is an extension or
43
+ // DNS filter blocking it — a cause the user can fix in ten seconds if they
44
+ // know it and cannot fix at all if they do not. Retrying does not help here,
45
+ // so "please try again" alone would be actively misleading.
46
+ recaptcha_missing:
47
+ 'Please complete the verification check on the sign-in page and try again. If it never appeared, an ad blocker or network filter may be blocking www.google.com/recaptcha.',
48
+ recaptcha_invalid:
49
+ 'That verification check could not be confirmed. Please try again.',
50
+ mailbox_already_registered:
51
+ 'An account already exists for this email address. Try signing in instead.',
52
+ privacy_egress:
53
+ 'Accounts cannot be created over a VPN, proxy, or hosting provider. Please turn it off and try again — you can turn it back on afterwards.',
54
+ untrusted_client_ip:
55
+ 'We could not verify where this request came from. Please try again, or contact support if it keeps happening.',
56
+ ip_signup_velocity:
57
+ 'Too many accounts have been created from this network today. Please try again tomorrow, or contact support if you are on a shared connection.',
58
+ prefix_signup_velocity:
59
+ 'Too many accounts have been created from this network today. Please try again tomorrow, or contact support if you are on a shared connection.',
60
+ }
61
+
62
+ /** True when `code` is a reason this module has copy for. Narrows an untrusted
63
+ * query-string value without a cast at every call site. */
64
+ export function isSignupBlockReason(
65
+ code: string | null | undefined,
66
+ ): code is SignupBlockReason {
67
+ return Boolean(code) && code! in SIGNUP_BLOCK_MESSAGES
68
+ }