@alumbwe/anvil 1.0.41 → 1.0.49

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 (241) hide show
  1. package/bin/anvil.js +18 -0
  2. package/package.json +13 -6
  3. package/src/agents/bundled-agents.generated.ts +1 -1
  4. package/src/entry.ts +5 -0
  5. package/src/index.tsx +27 -4
  6. package/src/login/plain-login.ts +60 -0
  7. package/src/login/supabase-auth.ts +346 -0
  8. package/src/pre-init/client-env.ts +17 -0
  9. package/src/utils/anvil-client.ts +4 -0
  10. package/src/utils/auth.ts +16 -4
  11. package/src/utils/client-env-defaults.ts +28 -0
  12. package/src/utils/config-dir.ts +1 -1
  13. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +30 -1
  14. package/vendor/@anvil/common/src/util/project-ignore.ts +4 -3
  15. package/vendor/@anvil/sdk/dist/index.cjs +46 -10
  16. package/vendor/@anvil/sdk/dist/index.mjs +46 -10
  17. package/src/commands/__tests__/anvil-command-aliases.test.ts +0 -56
  18. package/src/commands/__tests__/bash-command.test.ts +0 -445
  19. package/src/commands/__tests__/command-args.test.ts +0 -349
  20. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  21. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  22. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  23. package/src/commands/__tests__/image.test.ts +0 -99
  24. package/src/commands/__tests__/init.test.ts +0 -456
  25. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  26. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  27. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  28. package/src/commands/__tests__/router-input.test.ts +0 -298
  29. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  30. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +0 -91
  31. package/src/components/__tests__/anvil-model-selector.test.tsx +0 -519
  32. package/src/components/__tests__/anvil-offer-invariants.test.ts +0 -54
  33. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +0 -212
  34. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  35. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  36. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  37. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  38. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  39. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  40. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  41. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  42. package/src/components/__tests__/selectable-list.test.ts +0 -232
  43. package/src/components/__tests__/status-bar.test.tsx +0 -44
  44. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  45. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  46. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  47. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  48. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  49. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  50. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  51. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  52. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  53. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  54. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  55. package/src/data/slash-commands.test.ts +0 -24
  56. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +0 -49
  57. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  58. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  59. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  60. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  61. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  62. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  63. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  64. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  65. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  66. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  67. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  68. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  69. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  70. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  71. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  72. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  73. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  74. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  75. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  76. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  77. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  78. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  79. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  80. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  81. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  82. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  83. package/src/init/__tests__/init-direnv.test.ts +0 -526
  84. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  85. package/src/state/__tests__/feedback-store.test.ts +0 -257
  86. package/src/utils/__tests__/active-run.test.ts +0 -140
  87. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  88. package/src/utils/__tests__/agent-display.test.ts +0 -139
  89. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  90. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  91. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  92. package/src/utils/__tests__/anvil-instance-owner.test.ts +0 -69
  93. package/src/utils/__tests__/anvil-model-navigation.test.ts +0 -102
  94. package/src/utils/__tests__/anvil-premium-reset.test.ts +0 -79
  95. package/src/utils/__tests__/anvil-referral-cache.test.ts +0 -116
  96. package/src/utils/__tests__/anvil-session-api.test.ts +0 -215
  97. package/src/utils/__tests__/anvil-session-display.test.ts +0 -23
  98. package/src/utils/__tests__/anvil-streak-line.test.ts +0 -217
  99. package/src/utils/__tests__/arrays.test.ts +0 -264
  100. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  101. package/src/utils/__tests__/block-processor.test.ts +0 -810
  102. package/src/utils/__tests__/chat-history.test.ts +0 -203
  103. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  104. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  105. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  106. package/src/utils/__tests__/clipboard.test.ts +0 -910
  107. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  108. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  109. package/src/utils/__tests__/error-handling.test.ts +0 -658
  110. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  111. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  112. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  113. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  114. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  115. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  116. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  117. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  118. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  119. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  120. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  121. package/src/utils/__tests__/image-processor.test.ts +0 -209
  122. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  123. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  124. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  125. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  126. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  127. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  128. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  129. package/src/utils/__tests__/message-updater.test.ts +0 -645
  130. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  131. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  132. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  133. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  134. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  135. package/src/utils/__tests__/safe-json.test.ts +0 -77
  136. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  137. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  138. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  139. package/src/utils/__tests__/settings.test.ts +0 -99
  140. package/src/utils/__tests__/strings.test.ts +0 -192
  141. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  142. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  143. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  144. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  145. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  146. package/src/utils/__tests__/text-layout.test.ts +0 -82
  147. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  148. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  149. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  150. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  151. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  152. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  153. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  154. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  155. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +0 -846
  156. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +0 -101
  157. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +0 -1408
  158. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +0 -258
  159. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +0 -48
  160. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +0 -282
  161. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +0 -526
  162. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +0 -68
  163. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +0 -132
  164. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +0 -422
  165. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +0 -167
  166. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +0 -145
  167. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +0 -234
  168. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +0 -379
  169. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +0 -807
  170. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +0 -154
  171. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +0 -246
  172. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +0 -77
  173. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +0 -89
  174. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +0 -298
  175. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +0 -116
  176. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +0 -167
  177. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +0 -132
  178. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +0 -72
  179. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +0 -30
  180. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +0 -63
  181. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +0 -246
  182. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +0 -106
  183. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +0 -351
  184. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +0 -169
  185. package/vendor/@anvil/common/src/testing/errors.ts +0 -33
  186. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +0 -334
  187. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +0 -6
  188. package/vendor/@anvil/common/src/testing/index.ts +0 -84
  189. package/vendor/@anvil/common/src/testing/mock-modules.ts +0 -53
  190. package/vendor/@anvil/common/src/testing/mock-types.ts +0 -123
  191. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +0 -261
  192. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +0 -93
  193. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +0 -218
  194. package/vendor/@anvil/common/src/testing/mocks/database.ts +0 -337
  195. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +0 -219
  196. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +0 -166
  197. package/vendor/@anvil/common/src/testing/mocks/index.ts +0 -101
  198. package/vendor/@anvil/common/src/testing/mocks/logger.ts +0 -135
  199. package/vendor/@anvil/common/src/testing/mocks/stream.ts +0 -313
  200. package/vendor/@anvil/common/src/testing/mocks/timers.ts +0 -132
  201. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +0 -127
  202. package/vendor/@anvil/common/src/testing/setup.ts +0 -282
  203. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +0 -34
  204. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +0 -213
  205. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +0 -47
  206. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +0 -65
  207. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +0 -49
  208. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +0 -20
  209. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +0 -26
  210. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +0 -122
  211. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +0 -102
  212. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +0 -143
  213. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +0 -202
  214. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +0 -193
  215. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +0 -140
  216. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +0 -148
  217. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +0 -115
  218. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +0 -57
  219. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +0 -774
  220. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +0 -206
  221. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +0 -173
  222. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +0 -60
  223. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +0 -31
  224. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +0 -1252
  225. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +0 -505
  226. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +0 -22
  227. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +0 -90
  228. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +0 -325
  229. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +0 -28
  230. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +0 -130
  231. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +0 -1008
  232. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +0 -289
  233. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +0 -239
  234. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +0 -70
  235. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +0 -108
  236. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +0 -180
  237. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +0 -24
  238. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +0 -88
  239. package/vendor/@anvil/common/src/util/skills.test.ts +0 -44
  240. package/vendor/@anvil/sdk/dist/index.cjs.map +0 -440
  241. package/vendor/@anvil/sdk/dist/index.mjs.map +0 -440
@@ -1,202 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
-
3
- import {
4
- addDaysToDateKey,
5
- calculateAnvilStreak,
6
- getAnvilDailyStreakRewardPool,
7
- getAnvilStreakGlmBonusUnits,
8
- getAnvilUsageDateKey,
9
- } from '../anvil-streak'
10
-
11
- function usageDatesFrom(startDateKey: string, count: number): string[] {
12
- return Array.from({ length: count }, (_, index) =>
13
- addDaysToDateKey(startDateKey, index),
14
- )
15
- }
16
-
17
- describe('anvil streak helpers', () => {
18
- test('formats usage dates in the Anvil reset timezone', () => {
19
- expect(getAnvilUsageDateKey(new Date('2026-05-27T06:30:00.000Z'))).toBe(
20
- '2026-05-26',
21
- )
22
- expect(getAnvilUsageDateKey(new Date('2026-05-27T08:30:00.000Z'))).toBe(
23
- '2026-05-27',
24
- )
25
- })
26
-
27
- test('adds days across month boundaries', () => {
28
- expect(addDaysToDateKey('2026-03-01', -1)).toBe('2026-02-28')
29
- expect(addDaysToDateKey('2024-03-01', -1)).toBe('2024-02-29')
30
- expect(addDaysToDateKey('2026-12-31', 1)).toBe('2027-01-01')
31
- })
32
-
33
- test('counts a streak that includes today', () => {
34
- expect(
35
- calculateAnvilStreak({
36
- todayDateKey: '2026-05-27',
37
- usageDates: ['2026-05-25', '2026-05-23', '2026-05-27', '2026-05-26'],
38
- }),
39
- ).toEqual({
40
- streak: 3,
41
- todayUsed: true,
42
- lastUsageDate: '2026-05-27',
43
- })
44
- })
45
-
46
- test('keeps yesterday-anchored streaks alive before today is used', () => {
47
- expect(
48
- calculateAnvilStreak({
49
- todayDateKey: '2026-05-27',
50
- usageDates: ['2026-05-26', '2026-05-25', '2026-05-24'],
51
- }),
52
- ).toEqual({
53
- streak: 3,
54
- todayUsed: false,
55
- lastUsageDate: '2026-05-26',
56
- })
57
- })
58
-
59
- test('returns zero after a missed full day', () => {
60
- expect(
61
- calculateAnvilStreak({
62
- todayDateKey: '2026-05-27',
63
- usageDates: ['2026-05-25', '2026-05-24'],
64
- }),
65
- ).toEqual({
66
- streak: 0,
67
- todayUsed: false,
68
- lastUsageDate: '2026-05-25',
69
- })
70
- })
71
- })
72
-
73
- describe('anvil streak rewards', () => {
74
- test('grants one weekly GLM session per completed 7 days, capped at 4', () => {
75
- const expected: Array<[streak: number, units: number]> = [
76
- [7, 1],
77
- [8, 1],
78
- [13, 1],
79
- [14, 2],
80
- [21, 3],
81
- [28, 4],
82
- [42, 4],
83
- ]
84
- for (const [streak, units] of expected) {
85
- expect(
86
- getAnvilStreakGlmBonusUnits({
87
- todayDateKey: addDaysToDateKey('2026-07-01', streak - 1),
88
- usageDates: usageDatesFrom('2026-07-01', streak),
89
- }),
90
- ).toBe(units)
91
- }
92
- })
93
-
94
- test('returns zero for empty and six-day usage histories', () => {
95
- expect(
96
- getAnvilStreakGlmBonusUnits({
97
- todayDateKey: '2026-07-13',
98
- usageDates: [],
99
- }),
100
- ).toBe(0)
101
- expect(
102
- getAnvilStreakGlmBonusUnits({
103
- todayDateKey: '2026-07-13',
104
- usageDates: usageDatesFrom('2026-07-07', 6),
105
- }),
106
- ).toBe(0)
107
- })
108
-
109
- test('a yesterday-anchored 28-day streak earns the max through the bounded window', () => {
110
- // Mirrors the web quota query, which fetches only 28 days back from today:
111
- // the oldest still-active max-tier streak (today unused) must be fully
112
- // visible inside that window.
113
- expect(
114
- getAnvilStreakGlmBonusUnits({
115
- todayDateKey: addDaysToDateKey('2026-07-01', 28),
116
- usageDates: usageDatesFrom('2026-07-01', 28),
117
- }),
118
- ).toBe(4)
119
- })
120
-
121
- test('refills GLM before the first use of a new week while the streak is alive', () => {
122
- expect(
123
- getAnvilStreakGlmBonusUnits({
124
- todayDateKey: '2026-07-13',
125
- usageDates: usageDatesFrom('2026-07-06', 7),
126
- }),
127
- ).toBe(1)
128
- })
129
-
130
- test('removes the live GLM entitlement after a missed full day', () => {
131
- expect(
132
- getAnvilStreakGlmBonusUnits({
133
- todayDateKey: '2026-07-14',
134
- usageDates: usageDatesFrom('2026-07-06', 7),
135
- }),
136
- ).toBe(0)
137
- })
138
-
139
- test('full access persists only the daily premium bonus', () => {
140
- expect(
141
- getAnvilDailyStreakRewardPool({
142
- streak: 7,
143
- todayUsed: true,
144
- accessTier: 'full',
145
- }),
146
- ).toBe('premium')
147
- })
148
-
149
- test('full access grants the daily premium bonus throughout the streak', () => {
150
- expect(
151
- getAnvilDailyStreakRewardPool({
152
- streak: 8,
153
- todayUsed: true,
154
- accessTier: 'full',
155
- }),
156
- ).toBe('premium')
157
- expect(
158
- getAnvilDailyStreakRewardPool({
159
- streak: 13,
160
- todayUsed: true,
161
- accessTier: 'full',
162
- }),
163
- ).toBe('premium')
164
- })
165
-
166
- test('limited access grants the limited bonus every day at streak >= 7', () => {
167
- // The threshold day and later days both grant the bonus.
168
- expect(
169
- getAnvilDailyStreakRewardPool({
170
- streak: 14,
171
- todayUsed: true,
172
- accessTier: 'limited',
173
- }),
174
- ).toBe('limited')
175
- expect(
176
- getAnvilDailyStreakRewardPool({
177
- streak: 9,
178
- todayUsed: true,
179
- accessTier: 'limited',
180
- }),
181
- ).toBe('limited')
182
- })
183
-
184
- test('no daily reward below seven days or before today is used', () => {
185
- expect(
186
- getAnvilDailyStreakRewardPool({
187
- streak: 6,
188
- todayUsed: true,
189
- accessTier: 'full',
190
- }),
191
- ).toBeNull()
192
- // Streak is at 7 only because yesterday anchored it; the user hasn't used
193
- // Anvil today, so no bonus is earned yet.
194
- expect(
195
- getAnvilDailyStreakRewardPool({
196
- streak: 7,
197
- todayUsed: false,
198
- accessTier: 'full',
199
- }),
200
- ).toBeNull()
201
- })
202
- })
@@ -1,193 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
-
3
- import {
4
- buildAnvilUsageGrid,
5
- buildAnvilUsageSummary,
6
- calculateLongestAnvilStreak,
7
- } from '../anvil-usage-summary'
8
-
9
- describe('calculateLongestAnvilStreak', () => {
10
- it('finds the longest run rather than the most recent one', () => {
11
- expect(
12
- calculateLongestAnvilStreak([
13
- '2026-08-01',
14
- '2026-08-02',
15
- '2026-08-03',
16
- '2026-08-04',
17
- // gap
18
- '2026-08-10',
19
- '2026-08-11',
20
- ]),
21
- ).toBe(4)
22
- })
23
-
24
- it('does not extend a run across a gap, unlike the current streak', () => {
25
- // The current streak deliberately survives "used yesterday, not yet today"
26
- // because the day is not over. A historical run has no such grace.
27
- expect(calculateLongestAnvilStreak(['2026-08-01', '2026-08-03'])).toBe(1)
28
- })
29
-
30
- it('counts each day once when the input repeats or arrives unsorted', () => {
31
- expect(
32
- calculateLongestAnvilStreak([
33
- '2026-08-03',
34
- '2026-08-01',
35
- '2026-08-02',
36
- '2026-08-02',
37
- ]),
38
- ).toBe(3)
39
- })
40
-
41
- it('is zero with no history', () => {
42
- expect(calculateLongestAnvilStreak([])).toBe(0)
43
- })
44
-
45
- it('handles a run across a month boundary', () => {
46
- expect(
47
- calculateLongestAnvilStreak([
48
- '2026-07-30',
49
- '2026-07-31',
50
- '2026-08-01',
51
- ]),
52
- ).toBe(3)
53
- })
54
- })
55
-
56
- describe('buildAnvilUsageSummary', () => {
57
- const activeDates = ['2026-08-10', '2026-08-11', '2026-08-12']
58
-
59
- it('reports the current and longest streak together', () => {
60
- const summary = buildAnvilUsageSummary({
61
- activeDates: [
62
- // A four-day run that has since lapsed...
63
- '2026-07-01',
64
- '2026-07-02',
65
- '2026-07-03',
66
- '2026-07-04',
67
- // ...and a shorter one running right now.
68
- '2026-08-11',
69
- '2026-08-12',
70
- ],
71
- todayDateKey: '2026-08-12',
72
- })
73
-
74
- expect(summary.streak.current).toBe(2)
75
- expect(summary.streak.longest).toBe(4)
76
- expect(summary.streak.todayUsed).toBe(true)
77
- expect(summary.streak.lastUsageDate).toBe('2026-08-12')
78
- })
79
-
80
- it('counts all-time active days regardless of the map window', () => {
81
- const summary = buildAnvilUsageSummary({
82
- activeDates,
83
- todayDateKey: '2026-08-12',
84
- windowDays: 2,
85
- })
86
-
87
- // The window covers 08-11 and 08-12; 08-10 falls outside it but still
88
- // counts toward the account's history.
89
- expect(summary.activeDates).toEqual(['2026-08-11', '2026-08-12'])
90
- expect(summary.allTimeActiveDays).toBe(3)
91
- })
92
-
93
- it('drops days after today rather than letting a fast clock inflate totals', () => {
94
- const summary = buildAnvilUsageSummary({
95
- activeDates: [...activeDates, '2026-08-13'],
96
- todayDateKey: '2026-08-12',
97
- })
98
-
99
- expect(summary.allTimeActiveDays).toBe(3)
100
- expect(summary.streak.lastUsageDate).toBe('2026-08-12')
101
- })
102
-
103
- it('sorts and dedupes unordered input before walking the streak', () => {
104
- const summary = buildAnvilUsageSummary({
105
- activeDates: ['2026-08-12', '2026-08-10', '2026-08-11', '2026-08-11'],
106
- todayDateKey: '2026-08-12',
107
- })
108
-
109
- expect(summary.streak.current).toBe(3)
110
- expect(summary.activeDates).toEqual(activeDates)
111
- expect(summary.allTimeActiveDays).toBe(3)
112
- })
113
-
114
- it('carries a null recent window through rather than inventing zeros', () => {
115
- // The token aggregate caps itself with a statement timeout; a timeout must
116
- // read as "unknown" downstream, never as "you sent nothing".
117
- const summary = buildAnvilUsageSummary({
118
- activeDates,
119
- todayDateKey: '2026-08-12',
120
- recent: null,
121
- })
122
-
123
- expect(summary.recent).toBeNull()
124
- })
125
-
126
- it('passes the recent window through when it resolved', () => {
127
- const summary = buildAnvilUsageSummary({
128
- activeDates,
129
- todayDateKey: '2026-08-12',
130
- recent: {
131
- days: 7,
132
- messages: 12,
133
- inputTokens: 300,
134
- cacheReadTokens: 50,
135
- outputTokens: 50,
136
- totalTokens: 400,
137
- },
138
- })
139
-
140
- expect(summary.recent?.messages).toBe(12)
141
- expect(summary.recent?.totalTokens).toBe(400)
142
- })
143
- })
144
-
145
- describe('buildAnvilUsageGrid', () => {
146
- // 2026-08-12 is a Wednesday (weekday 3).
147
- const todayDateKey = '2026-08-12'
148
- const todayWeekday = 3
149
-
150
- it('pads to whole weeks so every row is one weekday', () => {
151
- const grid = buildAnvilUsageGrid({
152
- activeDates: [],
153
- todayDateKey,
154
- windowDays: 365,
155
- todayWeekday,
156
- })
157
-
158
- expect(grid.length % 7).toBe(0)
159
- })
160
-
161
- it('places today at its own weekday in the final column', () => {
162
- const grid = buildAnvilUsageGrid({
163
- activeDates: [],
164
- todayDateKey,
165
- windowDays: 365,
166
- todayWeekday,
167
- })
168
-
169
- const lastColumn = grid.slice(grid.length - 7)
170
- expect(lastColumn[todayWeekday]).toMatchObject({ date: todayDateKey })
171
- // Everything after today in the final column is padding, not a quiet day —
172
- // those dates have not happened yet.
173
- expect(lastColumn.slice(todayWeekday + 1).every((cell) => cell === null)).toBe(
174
- true,
175
- )
176
- })
177
-
178
- it('marks active days and fills the rest as quiet', () => {
179
- const grid = buildAnvilUsageGrid({
180
- activeDates: ['2026-08-11'],
181
- todayDateKey,
182
- windowDays: 3,
183
- todayWeekday,
184
- })
185
-
186
- const cells = grid.filter((cell) => cell !== null)
187
- expect(cells).toEqual([
188
- { date: '2026-08-10', active: false },
189
- { date: '2026-08-11', active: true },
190
- { date: '2026-08-12', active: false },
191
- ])
192
- })
193
- })
@@ -1,140 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
-
3
- import {
4
- CONTEXT_PRUNING_COMPLETED_EVENT,
5
- getAxiomOnlyLogEvent,
6
- STREAM_RECOVERY_EVENT,
7
- } from '../axiom-only-log'
8
-
9
- describe('getAxiomOnlyLogEvent', () => {
10
- test('sanitizes context-pruning metadata', () => {
11
- expect(
12
- getAxiomOnlyLogEvent({
13
- axiomEvent: CONTEXT_PRUNING_COMPLETED_EVENT,
14
- trigger_reason: 'context_limit',
15
- client_session_id: 'turn-123',
16
- dropped_user_entry_count: 2,
17
- live_user_prompt_text_preserved: true,
18
- prompt: 'must not leave the client',
19
- nested: { secret: true },
20
- context_token_count: Number.POSITIVE_INFINITY,
21
- }),
22
- ).toEqual({
23
- event: CONTEXT_PRUNING_COMPLETED_EVENT,
24
- data: {
25
- trigger_reason: 'context_limit',
26
- client_session_id: 'turn-123',
27
- dropped_user_entry_count: 2,
28
- live_user_prompt_text_preserved: true,
29
- },
30
- })
31
- })
32
-
33
- test('does not treat arbitrary events as Axiom-only', () => {
34
- expect(
35
- getAxiomOnlyLogEvent({
36
- axiomEvent: 'untrusted.event',
37
- prompt: 'secret',
38
- }),
39
- ).toBeNull()
40
- })
41
-
42
- test('does not treat an Object.prototype property name as a registered event', () => {
43
- // The event name is caller-supplied (any logger.*(data, msg) call sets
44
- // `data.axiomEvent`). Guards against ever matching it with a lookup keyed
45
- // on that name (e.g. a plain-object registry), where 'constructor' would
46
- // resolve through the prototype chain and get treated as registered —
47
- // shipping `{}` in place of the log's real payload. Must be rejected like
48
- // any other unknown event, via both the data-field and event-param path.
49
- for (const poisonEvent of [
50
- 'constructor',
51
- 'toString',
52
- 'hasOwnProperty',
53
- 'valueOf',
54
- '__proto__',
55
- ]) {
56
- expect(
57
- getAxiomOnlyLogEvent({
58
- axiomEvent: poisonEvent,
59
- prompt: 'must not be silently dropped',
60
- }),
61
- ).toBeNull()
62
- expect(
63
- getAxiomOnlyLogEvent({ prompt: 'must not be silently dropped' }, poisonEvent),
64
- ).toBeNull()
65
- }
66
- })
67
-
68
- test('sanitizes the client wire format identified by its top-level event', () => {
69
- expect(
70
- getAxiomOnlyLogEvent(
71
- {
72
- dropped_user_entry_count: 2,
73
- prompt: 'must not reach Axiom',
74
- },
75
- CONTEXT_PRUNING_COMPLETED_EVENT,
76
- ),
77
- ).toEqual({
78
- event: CONTEXT_PRUNING_COMPLETED_EVENT,
79
- data: { dropped_user_entry_count: 2 },
80
- })
81
- })
82
-
83
- test('accepts an allowlisted top-level event with empty data', () => {
84
- expect(getAxiomOnlyLogEvent(null, CONTEXT_PRUNING_COMPLETED_EVENT)).toEqual(
85
- {
86
- event: CONTEXT_PRUNING_COMPLETED_EVENT,
87
- data: {},
88
- },
89
- )
90
- })
91
-
92
- test('sanitizes stream-recovery metadata', () => {
93
- expect(
94
- getAxiomOnlyLogEvent({
95
- axiomEvent: STREAM_RECOVERY_EVENT,
96
- metric: 'stream_recovery_detected',
97
- source: 'stream-interrupted',
98
- model: 'openrouter/anthropic/claude-sonnet-4.5',
99
- agentId: 'base2',
100
- runId: 'run-123',
101
- userInputId: 'input-456',
102
- finishReason: 'unknown',
103
- hasYieldedContent: true,
104
- consecutive: 2,
105
- // Not in the allowlist: must not leak through.
106
- userId: 'user-789',
107
- message: 'must not leave the client',
108
- messageHistory: [{ role: 'user', content: 'secret' }],
109
- }),
110
- ).toEqual({
111
- event: STREAM_RECOVERY_EVENT,
112
- data: {
113
- metric: 'stream_recovery_detected',
114
- source: 'stream-interrupted',
115
- model: 'openrouter/anthropic/claude-sonnet-4.5',
116
- agentId: 'base2',
117
- runId: 'run-123',
118
- userInputId: 'input-456',
119
- finishReason: 'unknown',
120
- hasYieldedContent: true,
121
- consecutive: 2,
122
- },
123
- })
124
- })
125
-
126
- test('drops a stream-recovery field with the wrong value type', () => {
127
- // consecutive must be a number; a string value for it (or any other
128
- // type mismatch) is dropped rather than coerced.
129
- expect(
130
- getAxiomOnlyLogEvent({
131
- axiomEvent: STREAM_RECOVERY_EVENT,
132
- metric: 'stream_recovery_rescued',
133
- consecutive: '2',
134
- }),
135
- ).toEqual({
136
- event: STREAM_RECOVERY_EVENT,
137
- data: { metric: 'stream_recovery_rescued' },
138
- })
139
- })
140
- })
@@ -1,148 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
-
3
- import {
4
- UNRECOGNIZED_CLIENT,
5
- clientUserAgentFields,
6
- normalizeClientUserAgent,
7
- } from '../client-user-agent'
8
-
9
- describe('normalizeClientUserAgent', () => {
10
- it('parses the official CLI agent', () => {
11
- // What getCliAdRequestUserAgent() emits.
12
- expect(normalizeClientUserAgent('Anvil-CLI/0.0.138')).toEqual({
13
- product: 'anvil-cli',
14
- version: '0.0.138',
15
- })
16
- expect(normalizeClientUserAgent('Anvil-CLI/1.0.685')).toEqual({
17
- product: 'anvil-cli',
18
- version: '1.0.685',
19
- })
20
- })
21
-
22
- it('parses runtime and SDK agents a non-official client would send', () => {
23
- expect(normalizeClientUserAgent('Go-http-client/2.0')).toEqual({
24
- product: 'go-http-client',
25
- version: '2.0',
26
- })
27
- expect(normalizeClientUserAgent('Bun/1.3.11')).toEqual({
28
- product: 'bun',
29
- version: '1.3.11',
30
- })
31
- // The UA a published Anvil proxy sends. The second segment is not a
32
- // version, so it is dropped — `ai-sdk` alone still separates it from
33
- // `anvil-cli`, which is the whole point.
34
- expect(
35
- normalizeClientUserAgent('ai-sdk/openai-compatible/1.0.25/anvil'),
36
- ).toEqual({ product: 'ai-sdk' })
37
- })
38
-
39
- it('keeps only the leading product token, dropping platform detail', () => {
40
- // The PII guard: everything after the first whitespace is never read, so
41
- // OS/device strings cannot reach an event payload.
42
- const browser =
43
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
44
- expect(normalizeClientUserAgent(browser)).toEqual({
45
- product: 'mozilla',
46
- version: '5.0',
47
- })
48
- })
49
-
50
- it('returns undefined for an absent or blank header', () => {
51
- expect(normalizeClientUserAgent(undefined)).toBeUndefined()
52
- expect(normalizeClientUserAgent(null)).toBeUndefined()
53
- expect(normalizeClientUserAgent(' ')).toBeUndefined()
54
- expect(clientUserAgentFields(null)).toEqual({})
55
- })
56
-
57
- it('collapses an over-long product instead of truncating it', () => {
58
- // Truncating would both fabricate a name and leave long random strings as
59
- // a cheap way to mint new values.
60
- expect(normalizeClientUserAgent('A'.repeat(200) + '/1.0')).toEqual({
61
- product: UNRECOGNIZED_CLIENT,
62
- })
63
- // Exactly at the cap is still kept.
64
- const atCap = 'a'.repeat(32)
65
- expect(normalizeClientUserAgent(`${atCap}/1.0`)).toEqual({
66
- product: atCap,
67
- version: '1.0',
68
- })
69
- // One past it collapses.
70
- expect(normalizeClientUserAgent(`${'a'.repeat(33)}/1.0`)).toEqual({
71
- product: UNRECOGNIZED_CLIENT,
72
- })
73
- })
74
-
75
- it('collapses junk to a single constant rather than minting a value', () => {
76
- // The cardinality guard: a caller varying unparseable bytes cannot create
77
- // new distinct values.
78
- expect(normalizeClientUserAgent('!!!@@@###')).toEqual({
79
- product: UNRECOGNIZED_CLIENT,
80
- })
81
- expect(normalizeClientUserAgent('***/1.0')).toEqual({
82
- product: UNRECOGNIZED_CLIENT,
83
- })
84
- })
85
-
86
- it('rejects an address-shaped product rather than mangling it', () => {
87
- // Stripping `@` would record `userexample.com`, which is still identifying.
88
- expect(normalizeClientUserAgent('user@example.com/1.0')).toEqual({
89
- product: UNRECOGNIZED_CLIENT,
90
- })
91
- // A contact address in the conventional comment position is already gone:
92
- // everything after the first whitespace is never read.
93
- expect(
94
- normalizeClientUserAgent('MyBot/1.0 (+contact@example.com)'),
95
- ).toEqual({ product: 'mybot', version: '1.0' })
96
- })
97
-
98
- it('cannot inject fields into a log line', () => {
99
- // Newlines and CR terminate the token before any charset stripping.
100
- expect(normalizeClientUserAgent('evil\nFAKE-FIELD: 1/1.0')).toEqual({
101
- product: 'evil',
102
- })
103
- expect(normalizeClientUserAgent('evil\r\ninjected/1.0')).toEqual({
104
- product: 'evil',
105
- })
106
- expect(normalizeClientUserAgent('{"json":"inject"}/1.0')).toEqual({
107
- product: 'jsoninject',
108
- version: '1.0',
109
- })
110
- })
111
-
112
- it('drops a version that does not look like a version', () => {
113
- // Keeps free-form text (a plausible PII carrier) out of the version field.
114
- expect(normalizeClientUserAgent('curl/not-a-version')).toEqual({
115
- product: 'curl',
116
- })
117
- expect(normalizeClientUserAgent('someclient')).toEqual({
118
- product: 'someclient',
119
- })
120
- })
121
-
122
- it('drops an over-long version rather than fabricating a truncated one', () => {
123
- expect(normalizeClientUserAgent('x/1' + '2'.repeat(100))).toEqual({
124
- product: 'x',
125
- })
126
- // The case that makes truncation actively wrong: a real, legitimate semver
127
- // longer than the cap. Reporting `1.0.0-beta+build` would name a version
128
- // that does not exist and would collide with `...build.2`.
129
- expect(normalizeClientUserAgent('x/1.0.0-beta+build.1')).toEqual({
130
- product: 'x',
131
- })
132
- // Exactly at the cap is kept.
133
- expect(normalizeClientUserAgent('x/1234567890123456')).toEqual({
134
- product: 'x',
135
- version: '1234567890123456',
136
- })
137
- })
138
-
139
- it('omits the version key entirely when absent', () => {
140
- expect(clientUserAgentFields('someclient')).toEqual({
141
- client_ua_product: 'someclient',
142
- })
143
- expect(clientUserAgentFields('Anvil-CLI/0.0.138')).toEqual({
144
- client_ua_product: 'anvil-cli',
145
- client_ua_version: '0.0.138',
146
- })
147
- })
148
- })