@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.
- package/.github/workflows/ci.yml +22 -0
- package/.oxlintrc.json +8 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/assets/index-Dq74RfU-.css +1 -0
- package/dist/assets/index-_9e8Z58Z.js +66 -0
- package/dist/favicon.svg +1 -0
- package/dist/icons.svg +24 -0
- package/dist/index.html +14 -0
- package/index.html +13 -0
- package/package.json +45 -0
- package/packages/tokn/LICENSE +21 -0
- package/packages/tokn/README.md +85 -0
- package/packages/tokn/package.json +56 -0
- package/packages/tokn/src/aggregate.ts +83 -0
- package/packages/tokn/src/cli.ts +181 -0
- package/packages/tokn/src/cursor.ts +85 -0
- package/packages/tokn/src/index.ts +6 -0
- package/packages/tokn/src/node.ts +50 -0
- package/packages/tokn/src/parsers/claude.ts +53 -0
- package/packages/tokn/src/parsers/codex.ts +74 -0
- package/packages/tokn/src/pricing/index.ts +106 -0
- package/packages/tokn/src/pricing/prices.json +948 -0
- package/packages/tokn/src/pricing/prices.ts +947 -0
- package/packages/tokn/src/types.ts +51 -0
- package/packages/tokn/tests/__snapshots__/eval.test.ts.snap +12 -0
- package/packages/tokn/tests/aggregate.test.ts +73 -0
- package/packages/tokn/tests/claude.test.ts +87 -0
- package/packages/tokn/tests/codex.test.ts +54 -0
- package/packages/tokn/tests/cursor.test.ts +126 -0
- package/packages/tokn/tests/eval.test.ts +69 -0
- package/packages/tokn/tests/fixtures/claude-session.jsonl +3 -0
- package/packages/tokn/tests/fixtures/codex-session.jsonl +5 -0
- package/packages/tokn/tests/pricing.test.ts +137 -0
- package/packages/tokn/tsconfig.json +21 -0
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/scripts/validate.ts +122 -0
- package/src/App.tsx +12 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/vite.svg +1 -0
- package/src/index.css +250 -0
- package/src/lib/refresh.ts +134 -0
- package/src/lib/scanner.ts +108 -0
- package/src/main.tsx +7 -0
- package/src/ui/Dashboard.tsx +203 -0
- package/src/ui/Popover.tsx +91 -0
- package/src/ui/Settings.tsx +114 -0
- package/src/ui/useAppData.ts +71 -0
- package/src-tauri/Cargo.lock +5972 -0
- package/src-tauri/Cargo.toml +35 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +114 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/64x64.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png +0 -0
- package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png +0 -0
- package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png +0 -0
- package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/icons/ios/AppIcon-20x20@1x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-20x20@2x-1.png +0 -0
- package/src-tauri/icons/ios/AppIcon-20x20@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-20x20@3x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-29x29@1x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-29x29@2x-1.png +0 -0
- package/src-tauri/icons/ios/AppIcon-29x29@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-29x29@3x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-40x40@1x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-40x40@2x-1.png +0 -0
- package/src-tauri/icons/ios/AppIcon-40x40@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-40x40@3x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-512@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-60x60@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-60x60@3x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-76x76@1x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-76x76@2x.png +0 -0
- package/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png +0 -0
- package/src-tauri/icons/source/app-icon-1024.png +0 -0
- package/src-tauri/icons/source/app-icon.svg +74 -0
- package/src-tauri/icons/source/tray-22.png +0 -0
- package/src-tauri/icons/source/tray-44.png +0 -0
- package/src-tauri/icons/source/tray-icon.svg +14 -0
- package/src-tauri/permissions/allow-custom-commands.toml +11 -0
- package/src-tauri/src/lib.rs +238 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/tauri.conf.json +57 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +23 -0
- package/vite.config.ts +7 -0
- package/vitest.config.ts +28 -0
package/dist/favicon.svg
ADDED
|
@@ -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>
|
package/dist/index.html
ADDED
|
@@ -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';
|