tokentracker-cli 1.5.17 → 1.5.18

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 (113) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-BiEoQE7Z.js → AchievementBadge-DE1v2d68.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-4g0Q8O0U.js → AchievementsPage-B_8ON-kk.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-D5dyOWFj.js → AchievementsSection-DScfDUBu.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-Bz067XUw.js → BadgeDetailModal-Dto1Fw6b.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-CFiGrlrg.js → BotAnimated-D85msagA.js} +1 -1
  6. package/dashboard/dist/assets/{Card-CkYKahsn.js → Card-BY22bIUZ.js} +1 -1
  7. package/dashboard/dist/assets/{CloudCheckoutPage-BCzIRPkW.js → CloudCheckoutPage-BAdtoWBp.js} +1 -1
  8. package/dashboard/dist/assets/{CloudPage-C0MZTK-9.js → CloudPage-Bf-IHk6T.js} +2 -2
  9. package/dashboard/dist/assets/{CommandPalette-DwSGE7c-.js → CommandPalette-CGfkEV0N.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-B80ps9B5.js → CommunityStatsModal-DMSi6_nf.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-xthCJoY5.js → ConfirmModal-Z-WmnkDu.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-DZ9vKkCI.js → Controls-TVKhEDXR.js} +1 -1
  13. package/dashboard/dist/assets/{DashboardPage-C4SwA5lD.js → DashboardPage-D0bpXi9x.js} +1 -1
  14. package/dashboard/dist/assets/{DateRangePopover-D8ihZ5SK.js → DateRangePopover-Bjmg11sQ.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-OVnpUpAc.js → DevicePage-qbDI5e82.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-DpueWMdH.js → DialogClose-Bdoz1fn-.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-BahSe9KF.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-Be_-fYZA.js → DialogTitle-AFjJHlYC.js} +1 -1
  19. package/dashboard/dist/assets/{ElectricLogo-BJek75ac.js → ElectricLogo-ClLSjxoP.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-HIpnpyJ-.js → FadeIn-BFU8MJF8.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-Dj0guZ3R.js → HeaderGithubStar-YVASNIKr.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-B3p_QB2m.js → HoverTooltip-CmmQ-YbL.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-DrxLV2uL.js → IpCheckPage-Cfx0N7pC.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-C3RPJvXW.js → LandingPage-1nwzYTWH.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardPage-CkVKRCA7.js → LeaderboardPage-mabCyyx8.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProBadge-C39qylf0.js → LeaderboardProBadge-CpuiWnd7.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-D1DEHNRN.js → LeaderboardProfileModal-CHKqw-_G.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-CoBBl1-f.js → LeaderboardProfilePage-DOTGyQ2d.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-BnjPGpcE.js → LimitsPage-CG58C7jU.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-CqCRY_ls.js → LocalOnlyNotice-DlYoWPFa.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-BeA3kcgy.js → LoginCard-DCeeAwlk.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-D9S7DnuH.js → LoginModal-EiPXjhfX.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-CXRATGB_.js → LoginPage-CvBueMXL.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-0_l-kTr4.js → PetPage-G6OT7E9R.js} +2 -2
  35. package/dashboard/dist/assets/{PopoverPopup-CfPgHc44.js → PopoverPopup-YVaxWIuz.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-DtjD5jVb.js → ProviderIcon-BdLkrup9.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-Bt8QpFOe.js → ResetPasswordPage-CB46SfND.js} +1 -1
  38. package/dashboard/dist/assets/{Select-BgQXA6kK.js → Select-b_sgds1U.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-C7hWQpTf.js → SelectItemText-DPzAvIN5.js} +1 -1
  40. package/dashboard/dist/assets/{SelfHostPage-CUK_eL6K.js → SelfHostPage-ab1vM_Ap.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-CF0nAsAf.js → ServiceStatusPage-DWdqmWFE.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-CE2cz-4_.js → SessionsPage-D1lZMRLn.js} +1 -1
  43. package/dashboard/dist/assets/{SettingsPage-su2wAKfz.js → SettingsPage-DJ5SHzfe.js} +3 -3
  44. package/dashboard/dist/assets/{SkillsPage-Bp0PnSf_.js → SkillsPage-D1jKoc4w.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-CjY25cur.js → TokenGalaxy-D9FnMzuQ.js} +1 -1
  46. package/dashboard/dist/assets/{TooltipPopup-DDztBp5x.js → TooltipPopup-CXImEU-U.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-CDaOhv8o.js → TrendMonitor-D-QL4bKY.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-CjaQe3QN.js → WidgetsPage-DBxo2Fax.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-P4LVouC8.js → WrappedPage-CkQc1EjF.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-CzHn8J8J.js → __vite-browser-external-999JOlud.js} +2 -2
  51. package/dashboard/dist/assets/{agent-logos-CS9Iaf0t.js → agent-logos-C8Zq5jVJ.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-left-nH7mwAsq.js → arrow-left-Cc0vGlDY.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-right-BoRjpMCP.js → arrow-right-C2qOVlDC.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right-Cg0soM7i.js → arrow-up-right-wIUnZTy-.js} +1 -1
  55. package/dashboard/dist/assets/{calendar-days-6BSY0SCe.js → calendar-days-ZVqo5KcO.js} +1 -1
  56. package/dashboard/dist/assets/{check-Ldxu9X3c.js → check-VK1_HpZD.js} +1 -1
  57. package/dashboard/dist/assets/{chevron-down-Cl1UIsFC.js → chevron-down-DzN4aEbK.js} +1 -1
  58. package/dashboard/dist/assets/{cloud-checkout-CgoKsrLa.js → cloud-checkout-DY_OGdcU.js} +1 -1
  59. package/dashboard/dist/assets/{composite-CqtsbFSD.js → composite-CQdQrYyw.js} +1 -1
  60. package/dashboard/dist/assets/{copy-CHDv4TDb.js → copy-1CiGdscm.js} +1 -1
  61. package/dashboard/dist/assets/{download-bExf6Ccu.js → download-DnOkvUz6.js} +1 -1
  62. package/dashboard/dist/assets/{external-link-DPe3LHcW.js → external-link-Bb6RxSJQ.js} +1 -1
  63. package/dashboard/dist/assets/{flame-CYUEYQbn.js → flame-BzdRbf5R.js} +1 -1
  64. package/dashboard/dist/assets/{format-tokens-CJ73U0H5.js → format-tokens-CJDFj6UL.js} +1 -1
  65. package/dashboard/dist/assets/{gift-BCmo7fKV.js → gift-BD3tOkCz.js} +1 -1
  66. package/dashboard/dist/assets/{icons-BtAa7a8Q.js → icons-CuIU9GgV.js} +1 -1
  67. package/dashboard/dist/assets/{index--VJpPvce.js → index-3uw-etEr.js} +1 -1
  68. package/dashboard/dist/assets/{index-CyhRNpQp.js → index-CrBT_rtN.js} +1 -1
  69. package/dashboard/dist/assets/{index-CKwNx_s1.js → index-DIc6eZMw.js} +1 -1
  70. package/dashboard/dist/assets/{index-CSFt_XvV.js → index-DJ8zVWHq.js} +1 -1
  71. package/dashboard/dist/assets/{info-Cc5_kre6.js → info-B_S49Oq7.js} +1 -1
  72. package/dashboard/dist/assets/{link-2-_RZOZ7kJ.js → link-2-CcDQ8fqJ.js} +1 -1
  73. package/dashboard/dist/assets/{loader-circle-7GinPf0v.js → loader-circle-Q4LB_Etc.js} +1 -1
  74. package/dashboard/dist/assets/{local-usage-transfer-Cx5WBzma.js → local-usage-transfer-XT9tg8yv.js} +1 -1
  75. package/dashboard/dist/assets/{modifiers.esm-CO1bm3IT.js → modifiers.esm-C8foqc3F.js} +1 -1
  76. package/dashboard/dist/assets/{monitor-smartphone-CjBB2EQT.js → monitor-smartphone-CA5Qp4Zj.js} +1 -1
  77. package/dashboard/dist/assets/{play-B9AHJ0ir.js → play-C96stHVA.js} +1 -1
  78. package/dashboard/dist/assets/{plus-BYHC32xe.js → plus-BeyajWsi.js} +1 -1
  79. package/dashboard/dist/assets/{provider-display-N2l_YZhb.js → provider-display-C6hZ4Gv0.js} +1 -1
  80. package/dashboard/dist/assets/{react-DSS6zSQ7.js → react-ByeQdtM4.js} +1 -1
  81. package/dashboard/dist/assets/{safePolygon-BdbLh1jE.js → safePolygon-XH8gG2Un.js} +1 -1
  82. package/dashboard/dist/assets/{search-D89mjm04.js → search-BnUBcQPT.js} +1 -1
  83. package/dashboard/dist/assets/{server-PR9yTCiu.js → server-BJt2-SYZ.js} +1 -1
  84. package/dashboard/dist/assets/{shield-check-BYLC3hFn.js → shield-check-BXqFTLdE.js} +1 -1
  85. package/dashboard/dist/assets/{skills-api-CM8yP8ZQ.js → skills-api-D2zAFF2H.js} +1 -1
  86. package/dashboard/dist/assets/{sortable.esm-4l8igdkd.js → sortable.esm-BdezpKNF.js} +1 -1
  87. package/dashboard/dist/assets/{store-uku0hfBl.js → store-BCgczKmA.js} +1 -1
  88. package/dashboard/dist/assets/{terminal-CQsyMIKh.js → terminal-IBpppziO.js} +1 -1
  89. package/dashboard/dist/assets/{trash-Du6DwmPp.js → trash-DzLx8Iw6.js} +1 -1
  90. package/dashboard/dist/assets/{use-community-stats-BI2Vkwhm.js → use-community-stats-DPq4t1Vg.js} +1 -1
  91. package/dashboard/dist/assets/{use-latest-request-guard-AGXK-7W_.js → use-latest-request-guard-aZwiQ-a0.js} +1 -1
  92. package/dashboard/dist/assets/{use-limits-display-prefs-XaEErSEN.js → use-limits-display-prefs-B6-VcF_L.js} +1 -1
  93. package/dashboard/dist/assets/{use-native-settings-BzNwI3lk.js → use-native-settings-DjZ-dUXo.js} +1 -1
  94. package/dashboard/dist/assets/{use-ordered-list-BUkFot2m.js → use-ordered-list-diTHYtBP.js} +1 -1
  95. package/dashboard/dist/assets/use-reduced-motion-BDkV3tNz.js +1 -0
  96. package/dashboard/dist/assets/{use-transform-CRTGmr0C.js → use-transform-BoD4Vv44.js} +1 -1
  97. package/dashboard/dist/assets/{use-usage-limits-DMHAKEU7.js → use-usage-limits-IajzoyyH.js} +1 -1
  98. package/dashboard/dist/assets/{useCurrency-D7bYEIAY.js → useCurrency-CUURuFSP.js} +1 -1
  99. package/dashboard/dist/assets/{usePositioner-PRBIy_6t.js → usePositioner-BJtm9eLD.js} +1 -1
  100. package/dashboard/dist/assets/{useTokenFormat-CGacmoDz.js → useTokenFormat-Bqv8Ygf_.js} +1 -1
  101. package/dashboard/dist/assets/{zap-B8svUQzd.js → zap-BYL3i1NW.js} +1 -1
  102. package/dashboard/dist/index.html +1 -1
  103. package/dashboard/dist/ip-check.html +1 -1
  104. package/dashboard/dist/leaderboard.html +1 -1
  105. package/dashboard/dist/share.html +1 -1
  106. package/package.json +1 -1
  107. package/src/commands/status.js +2 -0
  108. package/src/lib/pricing/curated-overrides.json +66 -1
  109. package/src/lib/pricing/matcher.js +30 -4
  110. package/src/lib/pricing/seed-snapshot.json +1 -1
  111. package/src/lib/rollout.js +1 -0
  112. package/dashboard/dist/assets/DialogDescription-CNb40Z5w.js +0 -1
  113. package/dashboard/dist/assets/use-reduced-motion-Dl-5CnUW.js +0 -1
@@ -47,12 +47,61 @@
47
47
  "cache_write": 0,
48
48
  "note": "Cline's own gateway namespaces (`cline-free/*` free tier, `cline-pass/*` flat-rate Cline Pass). Cline's shipped model catalog declares pricing 0 for every id in both namespaces, so a turn there has no marginal per-token cost — same convention as PI_SUBSCRIPTION_SOURCES. The fuzzy entries at the top of `fuzzy` route both prefixes here BEFORE the model names can match a public rate (`cline-pass/glm-5.3` must not bill GLM-5.3 list price, `cline-free/deepseek-v4.1-flash` must not bill DeepSeek). Verified against Cline 0.0.32 (/Applications/Cline.app model catalog + `usageDelta` cost, which stays 0 for both namespaces). A Cline turn that DOES report a positive per-call cost is honoured first because `cline` is listed in SOURCES_WITH_AUTHORITATIVE_COST in pricing/index.js. Remove or re-point these entries when Cline publishes per-token pricing for either namespace."
49
49
  },
50
+ "claude-fable-5-2": {
51
+ "input": 8,
52
+ "output": 40,
53
+ "cache_read": 0.2,
54
+ "cache_write": 10,
55
+ "note": "Claude Fable 5.2 standard tier, verified 2026-11-10: https://platform.claude.com/docs/en/about-claude/pricing and https://platform.claude.com/docs/en/models/fable-5-2/overview. $8 / $40 per MTok input/output. cache_read is the published cache-hit rate ($0.20 / MTok, 0.025x base input \u2014 not the usual 0.1x). cache_write is the published 5-minute cache-write rate ($10 / MTok, 1.25x); the 1-hour write is $16 / MTok and cannot fit this single field. Channel prefixes (anthropic/, bedrock/, vertex_ai/, azure_ai/, openrouter/, github_copilot/, \u2026) do not change the rate. Fast mode is a separate SKU at $16/$80 \u2014 see claude-fable-5-2-fast. Floating -latest aliases are NOT pinned here."
56
+ },
57
+ "claude-fable-5.2": {
58
+ "input": 8,
59
+ "output": 40,
60
+ "cache_read": 0.2,
61
+ "cache_write": 10,
62
+ "note": "Dotted spelling of claude-fable-5-2 (OpenRouter anthropic/claude-fable-5.2, github_copilot/claude-fable-5.2). Same standard-tier rates, verified 2026-11-10: https://platform.claude.com/docs/en/about-claude/pricing. Its own exact key because normalizeClaudeModel only hyphenates sonnet/opus/haiku, so a dotted fable id never reaches claude-fable-5-2."
63
+ },
64
+ "anthropic/claude-fable-5.2": {
65
+ "input": 8,
66
+ "output": 40,
67
+ "cache_read": 0.2,
68
+ "cache_write": 10,
69
+ "note": "OpenRouter id for Claude Fable 5.2 (https://openrouter.ai/anthropic/claude-fable-5.2). Same standard-tier rates as claude-fable-5-2, verified 2026-11-10. Kept exact so a lookup without the claude source normalizer (which is what strips provider prefixes) still hits."
70
+ },
71
+ "anthropic/claude-fable-5.2-20261109": {
72
+ "input": 8,
73
+ "output": 40,
74
+ "cache_read": 0.2,
75
+ "cache_write": 10,
76
+ "note": "OpenRouter dated snapshot of anthropic/claude-fable-5.2 (canonical_slug anthropic/claude-fable-5.2-20261109, released 2026-11-09). Same standard-tier rates, verified 2026-11-10: https://platform.claude.com/docs/en/about-claude/pricing. The Anthropic model page itself publishes no dated snapshot id."
77
+ },
78
+ "anthropic/claude-fable-5-2": {
79
+ "input": 8,
80
+ "output": 40,
81
+ "cache_read": 0.2,
82
+ "cache_write": 10,
83
+ "note": "Hyphenated provider-qualified id for Claude Fable 5.2 (models.dev anthropic/claude-fable-5-2). Same standard-tier rates, verified 2026-11-10. Channel prefix does not change billing."
84
+ },
85
+ "claude-fable-5-2-fast": {
86
+ "input": 16,
87
+ "output": 80,
88
+ "cache_read": 0.4,
89
+ "cache_write": 20,
90
+ "note": "Claude Fable 5.2 fast mode, a separate SKU (Foundry deployment id claude-fable-5-2-fast; also speed=fast on the Claude API). Input/output are the published fast rates, 2x standard: $16 / $80 per MTok, verified 2026-11-10 at https://platform.claude.com/docs/en/about-claude/pricing and https://platform.claude.com/docs/en/build-with-claude/fast-mode. Those pages publish no fast-mode cache dollar amounts; they say prompt-caching multipliers apply on top of fast-mode pricing, so cache_read/cache_write use the same Fable 5.2 multipliers as the standard tier (0.025x \u2192 $0.40, 5-minute 1.25x \u2192 $20) rather than a published fast cache price. The 1-hour fast write (2x \u2192 $32) cannot fit this single field. Do not bill this id at the $8/$40 standard rate."
91
+ },
92
+ "claude-fable-5.2-fast": {
93
+ "input": 16,
94
+ "output": 80,
95
+ "cache_read": 0.4,
96
+ "cache_write": 20,
97
+ "note": "Dotted spelling of claude-fable-5-2-fast (vercel/anthropic/claude-fable-5.2-fast, anthropic/claude-fable-5.2-fast). Same fast-mode rates as claude-fable-5-2-fast, verified 2026-11-10. Its own exact key because the dotted form is not rewritten to the hyphenated one."
98
+ },
50
99
  "claude-fable-5": {
51
100
  "input": 10,
52
101
  "output": 50,
53
102
  "cache_read": 1,
54
103
  "cache_write": 12.5,
55
- "note": "Fable 5 \u2014 Anthropic's top tier above Opus. Not yet in LiteLLM. $10/$50 per MTok; cache_read 0.1x, cache_write 1.25x. Remove once LiteLLM carries it."
104
+ "note": "Fable 5 \u2014 Anthropic's top tier above Opus. Not yet in LiteLLM. $10/$50 per MTok; cache_read 0.1x, cache_write 1.25x. Remove once LiteLLM carries it. Fable 5.2 is a cheaper separate SKU \u2014 see claude-fable-5-2."
56
105
  },
57
106
  "claude-opus-5": {
58
107
  "input": 5,
@@ -935,6 +984,22 @@
935
984
  "match": "haiku-5.5",
936
985
  "ref": "claude-haiku-5.5"
937
986
  },
987
+ {
988
+ "match": "fable-5-2-fast",
989
+ "ref": "claude-fable-5-2-fast"
990
+ },
991
+ {
992
+ "match": "fable-5.2-fast",
993
+ "ref": "claude-fable-5.2-fast"
994
+ },
995
+ {
996
+ "match": "fable-5-2",
997
+ "ref": "claude-fable-5-2"
998
+ },
999
+ {
1000
+ "match": "fable-5.2",
1001
+ "ref": "claude-fable-5.2"
1002
+ },
938
1003
  {
939
1004
  "match": "claude-opus-5-fast",
940
1005
  "ref": "claude-opus-5-fast"
@@ -104,8 +104,8 @@ function normalizeClaudeModel(model) {
104
104
  .replace(/^-+|-+$/g, "")
105
105
  .replace(/-{2,}/g, "-");
106
106
 
107
- if (/^claude-(sonnet|opus|haiku)-\d+\.\d+/.test(m)) {
108
- return m.replace(/^(claude-(?:sonnet|opus|haiku)-\d+)\.(\d+)/, "$1-$2");
107
+ if (/^claude-(sonnet|opus|haiku|fable)-\d+\.\d+/.test(m)) {
108
+ return m.replace(/^(claude-(?:sonnet|opus|haiku|fable)-\d+)\.(\d+)/, "$1-$2");
109
109
  }
110
110
  if (/^(sonnet|opus|haiku)-\d+[.-]\d+/.test(m)) {
111
111
  return m
@@ -345,6 +345,17 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
345
345
  };
346
346
  }
347
347
 
348
+ // Floating Fable aliases (`claude-fable-latest` and every provider's
349
+ // `…/claude-fable-latest`) retarget on each release. Checked 2026-11-10: the
350
+ // Anthropic model overview and the Fable 5.2 pages do not document the alias,
351
+ // so nothing pins it to 5.2. Stop here, after exact lookups, so the
352
+ // fable-5.2 fuzzy needles and the broad fable matcher cannot price it, but
353
+ // an explicit exact entry added later still wins. Re-check on the next
354
+ // Fable release before pointing it anywhere.
355
+ if (/(?:^|\/)claude-fable-latest(?:$|[^a-z0-9])/.test(lower)) {
356
+ return { hit: false, source: "miss", value: null };
357
+ }
358
+
348
359
  // Kimi Code K2.8 Preview aliases must not inherit fuzzy `kimi` → kimi-k2.5 rates (#766).
349
360
  // Parser stores bare `k2.8`; raw `kimicoding` / `kimi-k2.8` / `kimicoding/k2.8` may still
350
361
  // appear in lookups. `kimi-for-coding` keeps its curated exact entry (resolved above).
@@ -382,10 +393,25 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
382
393
  if (!match || !ref) continue;
383
394
  const needle = match.toLowerCase();
384
395
  if (!curated.exact[ref]) continue;
385
- if (lower.includes(needle)) {
396
+ // The Fable 5.2 needles are version-bounded. A plain substring lets the
397
+ // dot-restored form of a dated Fable 5 id through: claude-fable-5-20250901
398
+ // becomes claude-fable-5.20250901, which contains "fable-5.2". fable-5-20
399
+ // contains "fable-5-2" the same way. The minor must end the id or be
400
+ // followed by a non-digit, so -20261109 and -20 stay off the 5.2 rate.
401
+ const bounded = /^fable-5[.-]2(?:-fast)?$/.test(needle)
402
+ ? new RegExp(`${needle.replace(".", "\\.")}(?:$|[^0-9])`)
403
+ : null;
404
+ const matches = (value) => (bounded ? bounded.test(value) : value.includes(needle));
405
+ if (matches(lower)) {
406
+ return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
407
+ }
408
+ if (dotForm && matches(dotForm)) {
386
409
  return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
387
410
  }
388
- if (dotForm && dotForm.includes(needle)) {
411
+ // Display names ("Claude Fable 5.2", "Fable 5.2 Fast") only reach this
412
+ // scan when no source normalizer ran. Fold spaces for the pinned 5.2
413
+ // needles only — a general fold would price unrelated display names.
414
+ if (bounded && bounded.test(lower.replace(/[^a-z0-9.]+/g, "-"))) {
389
415
  return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
390
416
  }
391
417
  }