@alumbwe/anvil 1.0.42 → 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 (238) 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/index.tsx +27 -4
  5. package/src/login/plain-login.ts +60 -0
  6. package/src/login/supabase-auth.ts +346 -0
  7. package/src/utils/anvil-client.ts +4 -0
  8. package/src/utils/auth.ts +16 -4
  9. package/src/utils/config-dir.ts +1 -1
  10. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +30 -1
  11. package/vendor/@anvil/common/src/util/project-ignore.ts +4 -3
  12. package/vendor/@anvil/sdk/dist/index.cjs +46 -10
  13. package/vendor/@anvil/sdk/dist/index.mjs +46 -10
  14. package/src/commands/__tests__/anvil-command-aliases.test.ts +0 -56
  15. package/src/commands/__tests__/bash-command.test.ts +0 -445
  16. package/src/commands/__tests__/command-args.test.ts +0 -349
  17. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  18. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  19. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  20. package/src/commands/__tests__/image.test.ts +0 -99
  21. package/src/commands/__tests__/init.test.ts +0 -456
  22. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  23. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  24. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  25. package/src/commands/__tests__/router-input.test.ts +0 -298
  26. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  27. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +0 -91
  28. package/src/components/__tests__/anvil-model-selector.test.tsx +0 -519
  29. package/src/components/__tests__/anvil-offer-invariants.test.ts +0 -54
  30. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +0 -212
  31. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  32. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  33. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  34. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  35. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  36. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  37. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  38. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  39. package/src/components/__tests__/selectable-list.test.ts +0 -232
  40. package/src/components/__tests__/status-bar.test.tsx +0 -44
  41. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  42. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  43. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  44. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  45. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  46. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  47. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  48. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  49. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  50. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  51. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  52. package/src/data/slash-commands.test.ts +0 -24
  53. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +0 -49
  54. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  55. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  56. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  57. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  58. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  59. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  60. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  61. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  62. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  63. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  64. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  65. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  66. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  67. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  68. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  69. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  70. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  71. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  72. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  73. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  74. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  75. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  76. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  77. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  78. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  79. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  80. package/src/init/__tests__/init-direnv.test.ts +0 -526
  81. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  82. package/src/state/__tests__/feedback-store.test.ts +0 -257
  83. package/src/utils/__tests__/active-run.test.ts +0 -140
  84. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  85. package/src/utils/__tests__/agent-display.test.ts +0 -139
  86. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  87. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  88. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  89. package/src/utils/__tests__/anvil-instance-owner.test.ts +0 -69
  90. package/src/utils/__tests__/anvil-model-navigation.test.ts +0 -102
  91. package/src/utils/__tests__/anvil-premium-reset.test.ts +0 -79
  92. package/src/utils/__tests__/anvil-referral-cache.test.ts +0 -116
  93. package/src/utils/__tests__/anvil-session-api.test.ts +0 -215
  94. package/src/utils/__tests__/anvil-session-display.test.ts +0 -23
  95. package/src/utils/__tests__/anvil-streak-line.test.ts +0 -217
  96. package/src/utils/__tests__/arrays.test.ts +0 -264
  97. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  98. package/src/utils/__tests__/block-processor.test.ts +0 -810
  99. package/src/utils/__tests__/chat-history.test.ts +0 -203
  100. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  101. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  102. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  103. package/src/utils/__tests__/clipboard.test.ts +0 -910
  104. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  105. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  106. package/src/utils/__tests__/error-handling.test.ts +0 -658
  107. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  108. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  109. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  110. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  111. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  112. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  113. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  114. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  115. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  116. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  117. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  118. package/src/utils/__tests__/image-processor.test.ts +0 -209
  119. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  120. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  121. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  122. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  123. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  124. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  125. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  126. package/src/utils/__tests__/message-updater.test.ts +0 -645
  127. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  128. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  129. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  130. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  131. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  132. package/src/utils/__tests__/safe-json.test.ts +0 -77
  133. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  134. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  135. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  136. package/src/utils/__tests__/settings.test.ts +0 -99
  137. package/src/utils/__tests__/strings.test.ts +0 -192
  138. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  139. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  140. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  141. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  142. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  143. package/src/utils/__tests__/text-layout.test.ts +0 -82
  144. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  145. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  146. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  147. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  148. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  149. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  150. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  151. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  152. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +0 -846
  153. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +0 -101
  154. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +0 -1408
  155. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +0 -258
  156. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +0 -48
  157. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +0 -282
  158. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +0 -526
  159. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +0 -68
  160. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +0 -132
  161. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +0 -422
  162. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +0 -167
  163. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +0 -145
  164. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +0 -234
  165. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +0 -379
  166. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +0 -807
  167. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +0 -154
  168. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +0 -246
  169. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +0 -77
  170. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +0 -89
  171. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +0 -298
  172. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +0 -116
  173. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +0 -167
  174. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +0 -132
  175. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +0 -72
  176. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +0 -30
  177. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +0 -63
  178. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +0 -246
  179. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +0 -106
  180. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +0 -351
  181. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +0 -169
  182. package/vendor/@anvil/common/src/testing/errors.ts +0 -33
  183. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +0 -334
  184. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +0 -6
  185. package/vendor/@anvil/common/src/testing/index.ts +0 -84
  186. package/vendor/@anvil/common/src/testing/mock-modules.ts +0 -53
  187. package/vendor/@anvil/common/src/testing/mock-types.ts +0 -123
  188. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +0 -261
  189. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +0 -93
  190. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +0 -218
  191. package/vendor/@anvil/common/src/testing/mocks/database.ts +0 -337
  192. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +0 -219
  193. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +0 -166
  194. package/vendor/@anvil/common/src/testing/mocks/index.ts +0 -101
  195. package/vendor/@anvil/common/src/testing/mocks/logger.ts +0 -135
  196. package/vendor/@anvil/common/src/testing/mocks/stream.ts +0 -313
  197. package/vendor/@anvil/common/src/testing/mocks/timers.ts +0 -132
  198. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +0 -127
  199. package/vendor/@anvil/common/src/testing/setup.ts +0 -282
  200. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +0 -34
  201. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +0 -213
  202. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +0 -47
  203. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +0 -65
  204. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +0 -49
  205. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +0 -20
  206. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +0 -26
  207. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +0 -122
  208. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +0 -102
  209. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +0 -143
  210. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +0 -202
  211. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +0 -193
  212. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +0 -140
  213. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +0 -148
  214. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +0 -115
  215. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +0 -57
  216. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +0 -774
  217. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +0 -206
  218. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +0 -173
  219. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +0 -60
  220. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +0 -31
  221. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +0 -1252
  222. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +0 -505
  223. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +0 -22
  224. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +0 -90
  225. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +0 -325
  226. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +0 -28
  227. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +0 -130
  228. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +0 -1008
  229. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +0 -289
  230. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +0 -239
  231. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +0 -70
  232. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +0 -108
  233. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +0 -180
  234. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +0 -24
  235. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +0 -88
  236. package/vendor/@anvil/common/src/util/skills.test.ts +0 -44
  237. package/vendor/@anvil/sdk/dist/index.cjs.map +0 -440
  238. 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
- })