@neuralmindlabs/tokn 0.1.0

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 (119) hide show
  1. package/.github/workflows/ci.yml +22 -0
  2. package/.oxlintrc.json +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +71 -0
  5. package/dist/assets/index-Dq74RfU-.css +1 -0
  6. package/dist/assets/index-_9e8Z58Z.js +66 -0
  7. package/dist/favicon.svg +1 -0
  8. package/dist/icons.svg +24 -0
  9. package/dist/index.html +14 -0
  10. package/index.html +13 -0
  11. package/package.json +45 -0
  12. package/packages/tokn/LICENSE +21 -0
  13. package/packages/tokn/README.md +85 -0
  14. package/packages/tokn/package.json +56 -0
  15. package/packages/tokn/src/aggregate.ts +83 -0
  16. package/packages/tokn/src/cli.ts +181 -0
  17. package/packages/tokn/src/cursor.ts +85 -0
  18. package/packages/tokn/src/index.ts +6 -0
  19. package/packages/tokn/src/node.ts +50 -0
  20. package/packages/tokn/src/parsers/claude.ts +53 -0
  21. package/packages/tokn/src/parsers/codex.ts +74 -0
  22. package/packages/tokn/src/pricing/index.ts +106 -0
  23. package/packages/tokn/src/pricing/prices.json +948 -0
  24. package/packages/tokn/src/pricing/prices.ts +947 -0
  25. package/packages/tokn/src/types.ts +51 -0
  26. package/packages/tokn/tests/__snapshots__/eval.test.ts.snap +12 -0
  27. package/packages/tokn/tests/aggregate.test.ts +73 -0
  28. package/packages/tokn/tests/claude.test.ts +87 -0
  29. package/packages/tokn/tests/codex.test.ts +54 -0
  30. package/packages/tokn/tests/cursor.test.ts +126 -0
  31. package/packages/tokn/tests/eval.test.ts +69 -0
  32. package/packages/tokn/tests/fixtures/claude-session.jsonl +3 -0
  33. package/packages/tokn/tests/fixtures/codex-session.jsonl +5 -0
  34. package/packages/tokn/tests/pricing.test.ts +137 -0
  35. package/packages/tokn/tsconfig.json +21 -0
  36. package/public/favicon.svg +1 -0
  37. package/public/icons.svg +24 -0
  38. package/scripts/validate.ts +122 -0
  39. package/src/App.tsx +12 -0
  40. package/src/assets/hero.png +0 -0
  41. package/src/assets/vite.svg +1 -0
  42. package/src/index.css +250 -0
  43. package/src/lib/refresh.ts +134 -0
  44. package/src/lib/scanner.ts +108 -0
  45. package/src/main.tsx +7 -0
  46. package/src/ui/Dashboard.tsx +203 -0
  47. package/src/ui/Popover.tsx +91 -0
  48. package/src/ui/Settings.tsx +114 -0
  49. package/src/ui/useAppData.ts +71 -0
  50. package/src-tauri/Cargo.lock +5972 -0
  51. package/src-tauri/Cargo.toml +35 -0
  52. package/src-tauri/build.rs +3 -0
  53. package/src-tauri/capabilities/default.json +114 -0
  54. package/src-tauri/icons/128x128.png +0 -0
  55. package/src-tauri/icons/128x128@2x.png +0 -0
  56. package/src-tauri/icons/32x32.png +0 -0
  57. package/src-tauri/icons/64x64.png +0 -0
  58. package/src-tauri/icons/Square107x107Logo.png +0 -0
  59. package/src-tauri/icons/Square142x142Logo.png +0 -0
  60. package/src-tauri/icons/Square150x150Logo.png +0 -0
  61. package/src-tauri/icons/Square284x284Logo.png +0 -0
  62. package/src-tauri/icons/Square30x30Logo.png +0 -0
  63. package/src-tauri/icons/Square310x310Logo.png +0 -0
  64. package/src-tauri/icons/Square44x44Logo.png +0 -0
  65. package/src-tauri/icons/Square71x71Logo.png +0 -0
  66. package/src-tauri/icons/Square89x89Logo.png +0 -0
  67. package/src-tauri/icons/StoreLogo.png +0 -0
  68. package/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  69. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png +0 -0
  70. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  71. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png +0 -0
  72. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png +0 -0
  73. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  74. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png +0 -0
  75. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png +0 -0
  76. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  77. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png +0 -0
  78. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
  79. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  80. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  81. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
  82. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  83. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  84. package/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
  85. package/src-tauri/icons/icon.icns +0 -0
  86. package/src-tauri/icons/icon.ico +0 -0
  87. package/src-tauri/icons/icon.png +0 -0
  88. package/src-tauri/icons/ios/AppIcon-20x20@1x.png +0 -0
  89. package/src-tauri/icons/ios/AppIcon-20x20@2x-1.png +0 -0
  90. package/src-tauri/icons/ios/AppIcon-20x20@2x.png +0 -0
  91. package/src-tauri/icons/ios/AppIcon-20x20@3x.png +0 -0
  92. package/src-tauri/icons/ios/AppIcon-29x29@1x.png +0 -0
  93. package/src-tauri/icons/ios/AppIcon-29x29@2x-1.png +0 -0
  94. package/src-tauri/icons/ios/AppIcon-29x29@2x.png +0 -0
  95. package/src-tauri/icons/ios/AppIcon-29x29@3x.png +0 -0
  96. package/src-tauri/icons/ios/AppIcon-40x40@1x.png +0 -0
  97. package/src-tauri/icons/ios/AppIcon-40x40@2x-1.png +0 -0
  98. package/src-tauri/icons/ios/AppIcon-40x40@2x.png +0 -0
  99. package/src-tauri/icons/ios/AppIcon-40x40@3x.png +0 -0
  100. package/src-tauri/icons/ios/AppIcon-512@2x.png +0 -0
  101. package/src-tauri/icons/ios/AppIcon-60x60@2x.png +0 -0
  102. package/src-tauri/icons/ios/AppIcon-60x60@3x.png +0 -0
  103. package/src-tauri/icons/ios/AppIcon-76x76@1x.png +0 -0
  104. package/src-tauri/icons/ios/AppIcon-76x76@2x.png +0 -0
  105. package/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png +0 -0
  106. package/src-tauri/icons/source/app-icon-1024.png +0 -0
  107. package/src-tauri/icons/source/app-icon.svg +74 -0
  108. package/src-tauri/icons/source/tray-22.png +0 -0
  109. package/src-tauri/icons/source/tray-44.png +0 -0
  110. package/src-tauri/icons/source/tray-icon.svg +14 -0
  111. package/src-tauri/permissions/allow-custom-commands.toml +11 -0
  112. package/src-tauri/src/lib.rs +238 -0
  113. package/src-tauri/src/main.rs +6 -0
  114. package/src-tauri/tauri.conf.json +57 -0
  115. package/tsconfig.app.json +27 -0
  116. package/tsconfig.json +7 -0
  117. package/tsconfig.node.json +23 -0
  118. package/vite.config.ts +7 -0
  119. package/vitest.config.ts +28 -0
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
package/dist/icons.svg ADDED
@@ -0,0 +1,24 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg">
2
+ <symbol id="bluesky-icon" viewBox="0 0 16 17">
3
+ <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
4
+ <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
5
+ </symbol>
6
+ <symbol id="discord-icon" viewBox="0 0 20 19">
7
+ <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
8
+ </symbol>
9
+ <symbol id="documentation-icon" viewBox="0 0 21 20">
10
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
11
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
12
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
13
+ </symbol>
14
+ <symbol id="github-icon" viewBox="0 0 19 19">
15
+ <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
16
+ </symbol>
17
+ <symbol id="social-icon" viewBox="0 0 20 20">
18
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
19
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
20
+ </symbol>
21
+ <symbol id="x-icon" viewBox="0 0 19 19">
22
+ <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
23
+ </symbol>
24
+ </svg>
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>tokn-scaffold</title>
8
+ <script type="module" crossorigin src="/assets/index-_9e8Z58Z.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Dq74RfU-.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>tokn-scaffold</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@neuralmindlabs/tokn",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "tokn": "./dist/cli.js"
6
+ },
7
+ "type": "module",
8
+ "workspaces": [
9
+ "packages/*"
10
+ ],
11
+ "scripts": {
12
+ "dev": "vite",
13
+ "build": "npm run build -w tokn && tsc -b && vite build",
14
+ "lint": "oxlint",
15
+ "preview": "vite preview",
16
+ "test": "npm run build -w tokn && vitest run",
17
+ "validate": "npm run build -w tokn && node scripts/validate.ts",
18
+ "tauri": "tauri",
19
+ "test:coverage": "npm run build -w tokn && vitest run --coverage"
20
+ },
21
+ "dependencies": {
22
+ "@tauri-apps/api": "^2.11.1",
23
+ "@tauri-apps/plugin-autostart": "^2.5.1",
24
+ "@tauri-apps/plugin-fs": "^2.5.1",
25
+ "@tauri-apps/plugin-http": "^2.5.9",
26
+ "@tauri-apps/plugin-opener": "^2.5.4",
27
+ "@tauri-apps/plugin-store": "^2.4.3",
28
+ "react": "^19.2.7",
29
+ "react-dom": "^19.2.7",
30
+ "recharts": "^3.9.2",
31
+ "tokn": "*"
32
+ },
33
+ "devDependencies": {
34
+ "@tauri-apps/cli": "^2.11.4",
35
+ "@types/node": "^24.13.2",
36
+ "@types/react": "^19.2.17",
37
+ "@types/react-dom": "^19.2.3",
38
+ "@vitejs/plugin-react": "^6.0.3",
39
+ "@vitest/coverage-v8": "^4.1.10",
40
+ "oxlint": "^1.71.0",
41
+ "typescript": "~6.0.2",
42
+ "vite": "^8.1.1",
43
+ "vitest": "^4.1.10"
44
+ }
45
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Suryakant Pandey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # tokn
2
+
3
+ One CLI for your daily AI spend across **Claude Code**, **Codex**, and **Cursor**.
4
+
5
+ ```
6
+ npx tokn
7
+ ```
8
+
9
+ ```
10
+ date claude codex cursor total
11
+ 2026-07-03 $0.92 - - $0.92
12
+ 2026-07-06 $31.74 $2.10 $1.60 $35.44
13
+ ──────────────────────────────────────────────────
14
+ total $32.65 $2.10 $1.60 $36.36
15
+ ```
16
+
17
+ ## Commands
18
+
19
+ ```bash
20
+ tokn # daily table, last 30 days
21
+ tokn daily -d 90 # wider window
22
+ tokn monthly # per calendar month
23
+ tokn today # today's total + per-tool breakdown
24
+ tokn --json # machine-readable (works with every command)
25
+ ```
26
+
27
+ ## Where the numbers come from
28
+
29
+ | Source | How | Meaning |
30
+ |---|---|---|
31
+ | Claude Code | `~/.claude/projects/**/*.jsonl`, parsed locally | API-equivalent value |
32
+ | Codex | `~/.codex/sessions/**/*.jsonl`, parsed locally | API-equivalent value |
33
+ | Cursor | cursor.com dashboard API (optional) | Actual billed dollars |
34
+
35
+ Nothing leaves your machine except the optional Cursor call to cursor.com itself.
36
+
37
+ **Why the math is right** (and differs from some other tools):
38
+
39
+ - Anthropic cache writes are priced per TTL — 5-minute writes at 1.25x the
40
+ input rate, 1-hour writes at 2x. Claude Code overwhelmingly uses 1-hour
41
+ caching; ignoring the split undercounts real cost by ~15%.
42
+ - Codex duplicates its `token_count` events. tokn takes deltas of the
43
+ session-cumulative counter instead of summing per-event usage, which stays
44
+ exact under duplication (tools that sum naively report roughly 2x).
45
+ - Claude messages are deduplicated by `message.id` + `requestId` across
46
+ resumed/forked sessions.
47
+ - Prices come from LiteLLM's community-maintained table, vendored at release.
48
+
49
+ If you're on Claude/ChatGPT subscriptions, local costs are what the same usage
50
+ would have cost via API — useful for judging whether the subscription pays for
51
+ itself, but not a bill.
52
+
53
+ ## Cursor (optional)
54
+
55
+ Cursor keeps usage server-side, so tokn needs your dashboard session cookie:
56
+ grab `WorkosCursorSessionToken` from a logged-in cursor.com tab (DevTools →
57
+ Application → Cookies), then:
58
+
59
+ ```bash
60
+ TOKN_CURSOR_COOKIE=<value> tokn
61
+ # or
62
+ tokn --cursor-cookie <value>
63
+ ```
64
+
65
+ This uses the same unofficial endpoint Cursor's own dashboard calls; it can
66
+ break without notice and the cookie expires every few weeks. tokn works fine
67
+ without it.
68
+
69
+ ## Library use
70
+
71
+ The parsing/pricing core is exported for programmatic use:
72
+
73
+ ```ts
74
+ import { buildRollups, parseClaudeSession, eventCostUSD } from 'tokn';
75
+ import { scanLocalSources } from 'tokn/node';
76
+
77
+ const rollups = buildRollups(scanLocalSources());
78
+ ```
79
+
80
+ Browser-safe (`tokn`) and Node-only (`tokn/node`) entry points are separate —
81
+ the same core powers a macOS menu bar app.
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "tokn",
3
+ "version": "0.1.0",
4
+ "description": "Unified daily token usage and cost for Claude Code, Codex, and Cursor \u2014 one CLI, correct math",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "tokn": "dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./node": {
16
+ "types": "./dist/node.d.ts",
17
+ "import": "./dist/node.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.json",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "claude",
34
+ "claude-code",
35
+ "codex",
36
+ "cursor",
37
+ "tokens",
38
+ "usage",
39
+ "cost",
40
+ "cli",
41
+ "ai"
42
+ ],
43
+ "devDependencies": {
44
+ "@types/node": "^24.13.2",
45
+ "typescript": "^5.5.3"
46
+ },
47
+ "author": "Suryakant Pandey <suryapandey0211@gmail.com>",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/neuralmindlabs/Tokn.git"
51
+ },
52
+ "homepage": "https://github.com/neuralmindlabs/Tokn#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/neuralmindlabs/Tokn/issues"
55
+ }
56
+ }
@@ -0,0 +1,83 @@
1
+ import type { DayRollup, Tool, ToolDayTotals, UsageEvent } from './types.ts';
2
+ import { eventCostUSD } from './pricing/index.ts';
3
+
4
+ /** date (YYYY-MM-DD) -> model -> totals; the common currency of all sources */
5
+ export type DaysMap = Record<string, Record<string, ToolDayTotals>>;
6
+
7
+ export interface SourceContribution {
8
+ tool: Tool;
9
+ project?: string;
10
+ days: DaysMap;
11
+ }
12
+
13
+ export function localDate(iso: string): string {
14
+ const d = new Date(iso);
15
+ const y = d.getFullYear();
16
+ const m = String(d.getMonth() + 1).padStart(2, '0');
17
+ const day = String(d.getDate()).padStart(2, '0');
18
+ return `${y}-${m}-${day}`;
19
+ }
20
+
21
+ export function emptyTotals(): ToolDayTotals {
22
+ return {
23
+ inputTokens: 0,
24
+ outputTokens: 0,
25
+ cacheWriteTokens: 0,
26
+ cacheReadTokens: 0,
27
+ costUSD: 0,
28
+ unpricedEvents: 0,
29
+ };
30
+ }
31
+
32
+ function addTotals(into: ToolDayTotals, from: ToolDayTotals) {
33
+ into.inputTokens += from.inputTokens;
34
+ into.outputTokens += from.outputTokens;
35
+ into.cacheWriteTokens += from.cacheWriteTokens;
36
+ into.cacheReadTokens += from.cacheReadTokens;
37
+ into.costUSD += from.costUSD;
38
+ into.unpricedEvents += from.unpricedEvents;
39
+ }
40
+
41
+ export function totalTokens(t: ToolDayTotals): number {
42
+ return t.inputTokens + t.outputTokens + t.cacheWriteTokens + t.cacheReadTokens;
43
+ }
44
+
45
+ /** Price events and fold them into the per-file DaysMap kept in scan state. */
46
+ export function eventsToDays(events: UsageEvent[]): DaysMap {
47
+ const days: DaysMap = {};
48
+ for (const e of events) {
49
+ const date = localDate(e.timestamp);
50
+ const byModel = (days[date] ??= {});
51
+ const t = (byModel[e.model] ??= emptyTotals());
52
+ t.inputTokens += e.inputTokens;
53
+ t.outputTokens += e.outputTokens;
54
+ t.cacheWriteTokens += e.cacheWriteTokens;
55
+ t.cacheReadTokens += e.cacheReadTokens;
56
+ const cost = eventCostUSD(e);
57
+ if (cost === undefined) t.unpricedEvents += 1;
58
+ else t.costUSD += cost;
59
+ }
60
+ return days;
61
+ }
62
+
63
+ /** Merge every source's contribution into date-keyed rollups, sorted ascending. */
64
+ export function buildRollups(sources: SourceContribution[]): DayRollup[] {
65
+ const byDate = new Map<string, DayRollup>();
66
+ for (const src of sources) {
67
+ for (const [date, byModel] of Object.entries(src.days)) {
68
+ let day = byDate.get(date);
69
+ if (!day) {
70
+ day = { date, byTool: {}, byModel: {}, byProject: {}, totalCostUSD: 0, totalTokens: 0 };
71
+ byDate.set(date, day);
72
+ }
73
+ for (const [model, t] of Object.entries(byModel)) {
74
+ addTotals((day.byTool[src.tool] ??= emptyTotals()), t);
75
+ addTotals((day.byModel[model] ??= emptyTotals()), t);
76
+ addTotals((day.byProject[src.project ?? '(unknown)'] ??= emptyTotals()), t);
77
+ day.totalCostUSD += t.costUSD;
78
+ day.totalTokens += totalTokens(t);
79
+ }
80
+ }
81
+ }
82
+ return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
83
+ }
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ import { buildRollups, totalTokens, localDate, fetchPrices } from './index.ts';
3
+ import type { DayRollup, Tool, ToolDayTotals } from './types.ts';
4
+ import { fetchCursorUsage } from './cursor.ts';
5
+ import { scanLocalSources } from './node.ts';
6
+
7
+ const TOOLS: Tool[] = ['claude', 'codex', 'cursor'];
8
+ const LABEL: Record<Tool, string> = { claude: 'Claude', codex: 'Codex', cursor: 'Cursor' };
9
+
10
+ interface Args {
11
+ command: 'daily' | 'monthly' | 'today';
12
+ days: number;
13
+ json: boolean;
14
+ cursorCookie?: string;
15
+ }
16
+
17
+ function parseArgs(argv: string[]): Args {
18
+ const args: Args = { command: 'daily', days: 30, json: false };
19
+ for (let i = 0; i < argv.length; i++) {
20
+ const a = argv[i];
21
+ if (a === 'daily' || a === 'monthly' || a === 'today') args.command = a;
22
+ else if (a === '--days' || a === '-d') args.days = Math.max(1, Number(argv[++i]) || 30);
23
+ else if (a === '--json') args.json = true;
24
+ else if (a === '--cursor-cookie') args.cursorCookie = argv[++i];
25
+ else if (a === '--help' || a === '-h') {
26
+ console.log(`tokn — unified AI token cost across Claude Code, Codex, and Cursor
27
+
28
+ Usage: tokn [command] [options]
29
+
30
+ Commands
31
+ daily cost per day (default)
32
+ monthly cost per calendar month
33
+ today today's total and per-tool breakdown
34
+
35
+ Options
36
+ -d, --days <n> window for daily view (default 30)
37
+ --json machine-readable output
38
+ --cursor-cookie <v> WorkosCursorSessionToken value (or TOKN_CURSOR_COOKIE env)
39
+ -h, --help
40
+
41
+ Claude Code and Codex are read from local session logs (~/.claude, ~/.codex)
42
+ and priced at API-equivalent rates. Cursor requires the session cookie and
43
+ reports actual billed dollars.`);
44
+ process.exit(0);
45
+ }
46
+ }
47
+ args.cursorCookie ??= process.env.TOKN_CURSOR_COOKIE;
48
+ return args;
49
+ }
50
+
51
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
52
+ const c = {
53
+ dim: (s: string) => (useColor ? `\x1b[2m${s}\x1b[0m` : s),
54
+ bold: (s: string) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
55
+ claude: (s: string) => (useColor ? `\x1b[38;5;173m${s}\x1b[0m` : s),
56
+ codex: (s: string) => (useColor ? `\x1b[38;5;36m${s}\x1b[0m` : s),
57
+ cursor: (s: string) => (useColor ? `\x1b[38;5;104m${s}\x1b[0m` : s),
58
+ };
59
+
60
+ const usd = (n: number) => (n === 0 ? '-' : `$${n.toFixed(2)}`);
61
+ const pad = (s: string, w: number) => s.padStart(w);
62
+ // ANSI escapes have zero display width but count in padStart, so pad first
63
+ const padc = (s: string, w: number, color: (x: string) => string) => color(pad(s, w));
64
+
65
+ function toolCost(r: DayRollup, tool: Tool): number {
66
+ return r.byTool[tool]?.costUSD ?? 0;
67
+ }
68
+
69
+ function printTable(rows: Array<{ label: string; r: Record<Tool, number> }>) {
70
+ const W = 10;
71
+ let header = c.bold(pad('date', 10));
72
+ for (const tool of TOOLS) {
73
+ header += padc(tool, W, c[tool]);
74
+ }
75
+ header += c.bold(pad('total', W));
76
+ console.log(header);
77
+
78
+ const sums: Record<Tool, number> = TOOLS.reduce((acc, tool) => ({ ...acc, [tool]: 0 }), {} as Record<Tool, number>);
79
+ for (const { label, r } of rows) {
80
+ let rowStr = pad(label, 10);
81
+ let total = 0;
82
+ for (const tool of TOOLS) {
83
+ sums[tool] += r[tool];
84
+ rowStr += padc(usd(r[tool]), W, c[tool]);
85
+ total += r[tool];
86
+ }
87
+ console.log(rowStr + c.bold(pad(usd(total), W)));
88
+ }
89
+ console.log(c.dim('─'.repeat(10 + W * (TOOLS.length + 1))));
90
+ let totalRow = c.bold(pad('total', 10));
91
+ let grand = 0;
92
+ for (const tool of TOOLS) {
93
+ totalRow += padc(usd(sums[tool]), W, c[tool]);
94
+ grand += sums[tool];
95
+ }
96
+ console.log(totalRow + c.bold(pad(usd(grand), W)));
97
+ }
98
+
99
+ async function main() {
100
+ const args = parseArgs(process.argv.slice(2));
101
+
102
+ await fetchPrices();
103
+
104
+ const sources = scanLocalSources();
105
+ let cursorNote = '';
106
+ if (args.cursorCookie) {
107
+ const cursor = await fetchCursorUsage(args.cursorCookie);
108
+ if (cursor.status === 'ok') sources.push({ tool: 'cursor', days: cursor.days });
109
+ else cursorNote = `cursor: ${cursor.status}${cursor.detail ? ` (${cursor.detail})` : ''}`;
110
+ } else {
111
+ cursorNote = 'cursor: not connected (set TOKN_CURSOR_COOKIE or --cursor-cookie)';
112
+ }
113
+
114
+ const rollups = buildRollups(sources);
115
+ const today = localDate(new Date().toISOString());
116
+
117
+ if (args.command === 'today') {
118
+ const r = rollups.find((x) => x.date === today);
119
+ if (args.json) {
120
+ console.log(JSON.stringify(r ?? { date: today, totalCostUSD: 0, byTool: {} }, null, 2));
121
+ return;
122
+ }
123
+ console.log(c.bold(`Today (${today}): ${usd(r?.totalCostUSD ?? 0)}`));
124
+ for (const tool of TOOLS) {
125
+ const t: ToolDayTotals | undefined = r?.byTool[tool];
126
+ if (t) console.log(` ${LABEL[tool].padEnd(8)} ${usd(t.costUSD).padStart(9)} ${c.dim(`${(totalTokens(t) / 1e6).toFixed(1)}M tokens`)}`);
127
+ }
128
+ if (cursorNote) console.log(c.dim(` ${cursorNote}`));
129
+ return;
130
+ }
131
+
132
+ if (args.command === 'monthly') {
133
+ const byMonth = new Map<string, Record<Tool, number>>();
134
+ for (const r of rollups) {
135
+ const m = r.date.slice(0, 7);
136
+ const cur: Record<Tool, number> = byMonth.get(m) ?? TOOLS.reduce((acc, tool) => ({ ...acc, [tool]: 0 }), {} as Record<Tool, number>);
137
+ for (const tool of TOOLS) cur[tool] += toolCost(r, tool);
138
+ byMonth.set(m, cur);
139
+ }
140
+ const rows = [...byMonth.entries()].sort().map(([label, r]) => ({ label, r }));
141
+ if (args.json) {
142
+ console.log(JSON.stringify(rows.map(({ label, r }) => ({ month: label, ...r, total: Object.values(r).reduce((sum, val) => sum + val, 0) })), null, 2));
143
+ return;
144
+ }
145
+ printTable(rows);
146
+ if (cursorNote) console.log(c.dim(cursorNote));
147
+ return;
148
+ }
149
+
150
+ const cutoff = new Date();
151
+ cutoff.setDate(cutoff.getDate() - (args.days - 1));
152
+ const cutoffStr = localDate(cutoff.toISOString());
153
+ const windowed = rollups.filter((r) => r.date >= cutoffStr);
154
+ if (args.json) {
155
+ console.log(
156
+ JSON.stringify(
157
+ windowed.map((r) => ({
158
+ date: r.date,
159
+ ...TOOLS.reduce((acc, tool) => ({ ...acc, [tool]: toolCost(r, tool) }), {} as Record<Tool, number>),
160
+ total: r.totalCostUSD,
161
+ totalTokens: r.totalTokens,
162
+ })),
163
+ null,
164
+ 2,
165
+ ),
166
+ );
167
+ return;
168
+ }
169
+ printTable(
170
+ windowed.map((r) => ({
171
+ label: r.date,
172
+ r: TOOLS.reduce((acc, tool) => ({ ...acc, [tool]: toolCost(r, tool) }), {} as Record<Tool, number>),
173
+ })),
174
+ );
175
+ console.log(c.dim(`Claude/Codex: API-equivalent value from local logs. Cursor: billed dollars.${cursorNote ? ` ${cursorNote}` : ''}`));
176
+ }
177
+
178
+ main().catch((e) => {
179
+ console.error('tokn failed:', e instanceof Error ? e.message : e);
180
+ process.exit(1);
181
+ });
@@ -0,0 +1,85 @@
1
+ import { emptyTotals, localDate, type DaysMap } from './aggregate.ts';
2
+
3
+ export type CursorStatus = 'ok' | 'unauthorized' | 'error';
4
+
5
+ export interface CursorResult {
6
+ status: CursorStatus;
7
+ days: DaysMap;
8
+ detail?: string;
9
+ }
10
+
11
+ interface UsageEventDisplay {
12
+ timestamp?: string | number;
13
+ model?: string;
14
+ tokenUsage?: {
15
+ inputTokens?: number;
16
+ outputTokens?: number;
17
+ cacheWriteTokens?: number;
18
+ cacheReadTokens?: number;
19
+ totalCents?: number;
20
+ };
21
+ requestsCosts?: number;
22
+ }
23
+
24
+ /**
25
+ * Pull usage events from cursor.com's dashboard API (unofficial — the same
26
+ * endpoints the web dashboard uses). Costs here are real billed cents, not
27
+ * estimates. Requires the WorkosCursorSessionToken session cookie.
28
+ *
29
+ * fetchImpl lets environments that need a CORS-free client substitute for
30
+ * global fetch (the Tauri webview passes @tauri-apps/plugin-http's fetch).
31
+ */
32
+ export async function fetchCursorUsage(
33
+ cookie: string,
34
+ opts: { sinceDays?: number; fetchImpl?: typeof globalThis.fetch } = {},
35
+ ): Promise<CursorResult> {
36
+ const { sinceDays = 95, fetchImpl = globalThis.fetch } = opts;
37
+ const days: DaysMap = {};
38
+ const endDate = Date.now();
39
+ const startDate = endDate - sinceDays * 24 * 60 * 60 * 1000;
40
+ const pageSize = 300;
41
+
42
+ for (let page = 1; page <= 40; page++) {
43
+ let res: Response;
44
+ try {
45
+ res = await fetchImpl('https://cursor.com/api/dashboard/get-filtered-usage-events', {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ Cookie: `WorkosCursorSessionToken=${cookie.trim()}`,
50
+ },
51
+ body: JSON.stringify({ teamId: 0, startDate: String(startDate), endDate: String(endDate), page, pageSize }),
52
+ });
53
+ } catch (e) {
54
+ return { status: 'error', days, detail: String(e) };
55
+ }
56
+ if (res.status === 401 || res.status === 403) return { status: 'unauthorized', days };
57
+ if (!res.ok) return { status: 'error', days, detail: `HTTP ${res.status}` };
58
+
59
+ let data: any;
60
+ try {
61
+ data = await res.json();
62
+ } catch {
63
+ // an HTML login page means the cookie no longer authenticates
64
+ return { status: 'unauthorized', days };
65
+ }
66
+ const events: UsageEventDisplay[] = data.usageEventsDisplay ?? data.usageEvents ?? [];
67
+ for (const ev of events) {
68
+ const ts = typeof ev.timestamp === 'string' ? Number(ev.timestamp) : ev.timestamp;
69
+ if (!ts || Number.isNaN(ts)) continue;
70
+ const date = localDate(new Date(ts).toISOString());
71
+ const model = ev.model || 'cursor';
72
+ const byModel = (days[date] ??= {});
73
+ const t = (byModel[model] ??= emptyTotals());
74
+ const u = ev.tokenUsage;
75
+ t.inputTokens += u?.inputTokens ?? 0;
76
+ t.outputTokens += u?.outputTokens ?? 0;
77
+ t.cacheWriteTokens += u?.cacheWriteTokens ?? 0;
78
+ t.cacheReadTokens += u?.cacheReadTokens ?? 0;
79
+ t.costUSD += (u?.totalCents ?? 0) / 100;
80
+ }
81
+ const total = Number(data.totalUsageEventsCount ?? 0);
82
+ if (events.length < pageSize || page * pageSize >= total) break;
83
+ }
84
+ return { status: 'ok', days };
85
+ }
@@ -0,0 +1,6 @@
1
+ export * from './types.ts';
2
+ export * from './aggregate.ts';
3
+ export * from './pricing/index.ts';
4
+ export { parseClaudeSession, type ParsedSession } from './parsers/claude.ts';
5
+ export { parseCodexSession } from './parsers/codex.ts';
6
+ export { fetchCursorUsage, type CursorResult, type CursorStatus } from './cursor.ts';