claude-session-viewer 0.2.6 → 0.3.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.
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-96{height:24rem}.h-full{height:100%}.h-screen{height:100vh}.w-1{width:.25rem}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-3{width:.75rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-col-resize{cursor:col-resize}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-blue-400{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-500\/20{border-color:#3b82f633}.border-cyan-500\/20{border-color:#06b6d433}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-green-500\/20{border-color:#22c55e33}.border-orange-500\/20{border-color:#f9731633}.border-pink-500\/20{border-color:#ec489933}.border-purple-500\/20{border-color:#a855f733}.border-transparent{border-color:transparent}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.bg-blue-900\/50{background-color:#1e3a8a80}.bg-cyan-500{--tw-bg-opacity: 1;background-color:rgb(6 182 212 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/10{background-color:#06b6d41a}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-800\/30{background-color:#1f29374d}.bg-gray-800\/50{background-color:#1f293780}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-900\/50{background-color:#11182780}.bg-gray-900\/70{background-color:#111827b3}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.bg-green-900\/30{background-color:#14532d4d}.bg-green-900\/50{background-color:#14532d80}.bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.bg-orange-500\/10{background-color:#f973161a}.bg-pink-500{--tw-bg-opacity: 1;background-color:rgb(236 72 153 / var(--tw-bg-opacity, 1))}.bg-pink-500\/10{background-color:#ec48991a}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity, 1))}.bg-purple-500\/10{background-color:#a855f71a}.bg-purple-700{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.bg-purple-900\/50{background-color:#581c8780}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-yellow-900\/30{background-color:#713f124d}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from: #3b82f6 var(--tw-gradient-from-position);--tw-gradient-to: rgb(59 130 246 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-green-500{--tw-gradient-to: #22c55e var(--tw-gradient-to-position)}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-14{padding-left:3.5rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-tight{line-height:1.25}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-purple-100{--tw-text-opacity: 1;color:rgb(243 232 255 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.\@container{container-type:inline-size}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}#root{width:100%;min-height:100vh}*{scrollbar-width:thin;scrollbar-color:#4B5563 #1F2937}*::-webkit-scrollbar{width:8px;height:8px}*::-webkit-scrollbar-track{background:#1f2937}*::-webkit-scrollbar-thumb{background-color:#4b5563;border-radius:4px}*::-webkit-scrollbar-thumb:hover{background-color:#6b7280}.last\:border-b-0:last-child{border-bottom-width:0px}.last\:pb-0:last-child{padding-bottom:0}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700\/50:hover{background-color:#37415180}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-600:hover{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.hover\:text-blue-400:hover{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}@container (min-width: 300px){.\@\[300px\]\:inline{display:inline}.\@\[300px\]\:hidden{display:none}}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width: 1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Claude Session Viewer</title>
8
- <script type="module" crossorigin src="/assets/index-CtzJJm9R.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BbXG5M-d.css">
8
+ <script type="module" crossorigin src="/assets/index-Ccni3k3B.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Rji9IRSo.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -221,6 +221,457 @@ async function getProjectSessions(projectPath) {
221
221
  }
222
222
  return allSessions;
223
223
  }
224
+ // Claude API Pricing (as of January 2025)
225
+ // Prices per 1M tokens
226
+ const PRICING = {
227
+ 'claude-sonnet-4-5-20250929': {
228
+ input: 3.0,
229
+ output: 15.0,
230
+ cacheCreation: 3.75,
231
+ cacheRead: 0.30
232
+ },
233
+ 'claude-sonnet-4-20250514': {
234
+ input: 3.0,
235
+ output: 15.0,
236
+ cacheCreation: 3.75,
237
+ cacheRead: 0.30
238
+ },
239
+ 'claude-opus-4-20250514': {
240
+ input: 15.0,
241
+ output: 75.0,
242
+ cacheCreation: 18.75,
243
+ cacheRead: 1.50
244
+ },
245
+ 'claude-haiku-4-20250515': {
246
+ input: 0.80,
247
+ output: 4.0,
248
+ cacheCreation: 1.0,
249
+ cacheRead: 0.08
250
+ }
251
+ };
252
+ // Helper: Calculate cost for token usage
253
+ function calculateCost(usage, model) {
254
+ const pricing = PRICING[model] || PRICING['claude-sonnet-4-5-20250929'];
255
+ return {
256
+ inputCost: (usage.inputTokens / 1000000) * pricing.input,
257
+ outputCost: (usage.outputTokens / 1000000) * pricing.output,
258
+ cacheCreationCost: (usage.cacheCreationTokens / 1000000) * pricing.cacheCreation,
259
+ cacheReadCost: (usage.cacheReadTokens / 1000000) * pricing.cacheRead,
260
+ totalCost: (usage.inputTokens / 1000000) * pricing.input +
261
+ (usage.outputTokens / 1000000) * pricing.output +
262
+ (usage.cacheCreationTokens / 1000000) * pricing.cacheCreation +
263
+ (usage.cacheReadTokens / 1000000) * pricing.cacheRead
264
+ };
265
+ }
266
+ // Helper: Extract token usage from message
267
+ function extractTokenUsage(message) {
268
+ if (message.type !== 'assistant' || !message.message?.usage) {
269
+ return null;
270
+ }
271
+ const usage = message.message.usage;
272
+ const model = message.message.model || 'unknown';
273
+ return {
274
+ usage: {
275
+ inputTokens: usage.input_tokens || 0,
276
+ cacheCreationTokens: usage.cache_creation_input_tokens || 0,
277
+ cacheReadTokens: usage.cache_read_input_tokens || 0,
278
+ outputTokens: usage.output_tokens || 0,
279
+ totalTokens: (usage.input_tokens || 0) +
280
+ (usage.cache_creation_input_tokens || 0) +
281
+ (usage.cache_read_input_tokens || 0) +
282
+ (usage.output_tokens || 0)
283
+ },
284
+ model
285
+ };
286
+ }
287
+ // Helper: Aggregate token usage
288
+ function aggregateTokenUsage(usages) {
289
+ return usages.reduce((acc, usage) => ({
290
+ inputTokens: acc.inputTokens + usage.inputTokens,
291
+ cacheCreationTokens: acc.cacheCreationTokens + usage.cacheCreationTokens,
292
+ cacheReadTokens: acc.cacheReadTokens + usage.cacheReadTokens,
293
+ outputTokens: acc.outputTokens + usage.outputTokens,
294
+ totalTokens: acc.totalTokens + usage.totalTokens
295
+ }), {
296
+ inputTokens: 0,
297
+ cacheCreationTokens: 0,
298
+ cacheReadTokens: 0,
299
+ outputTokens: 0,
300
+ totalTokens: 0
301
+ });
302
+ }
303
+ // API: Get token statistics
304
+ server.get('/api/statistics/tokens', async (request, reply) => {
305
+ try {
306
+ const projectsDir = join(CLAUDE_DIR, 'projects');
307
+ const projects = await readdir(projectsDir);
308
+ // Parse days parameter (default to 30)
309
+ const daysParam = request.query.days || '30';
310
+ const cutoffDate = new Date();
311
+ if (daysParam !== 'all') {
312
+ const days = parseInt(daysParam, 10);
313
+ cutoffDate.setDate(cutoffDate.getDate() - days);
314
+ cutoffDate.setHours(0, 0, 0, 0);
315
+ }
316
+ else {
317
+ // For 'all', set to a very old date to include everything
318
+ cutoffDate.setFullYear(2000, 0, 1);
319
+ }
320
+ // Data structures for aggregation
321
+ const dailyMap = new Map();
322
+ const projectMap = new Map();
323
+ const modelMap = new Map();
324
+ let totalMessages = 0;
325
+ let totalSessions = 0;
326
+ let minDate = null;
327
+ let maxDate = null;
328
+ let totalCacheCreation = 0;
329
+ let totalCacheRead = 0;
330
+ let ephemeral5mTokens = 0;
331
+ let ephemeral1hTokens = 0;
332
+ const allUsages = [];
333
+ const allCosts = [];
334
+ // Productivity metrics
335
+ const toolUsageMap = new Map();
336
+ let totalAgentSessions = 0;
337
+ // Trend analysis data structures
338
+ const hourlyMap = new Map();
339
+ const weekdayMap = new Map();
340
+ // Initialize hourly map (0-23)
341
+ for (let hour = 0; hour < 24; hour++) {
342
+ hourlyMap.set(hour, {
343
+ usage: { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0, totalTokens: 0 },
344
+ sessionIds: new Set(),
345
+ messageCount: 0
346
+ });
347
+ }
348
+ // Initialize weekday map (0-6, Sunday-Saturday)
349
+ for (let weekday = 0; weekday < 7; weekday++) {
350
+ weekdayMap.set(weekday, {
351
+ usage: { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0, totalTokens: 0 },
352
+ sessionIds: new Set(),
353
+ messageCount: 0
354
+ });
355
+ }
356
+ // Process all projects
357
+ for (const project of projects) {
358
+ const projectPath = join(projectsDir, project);
359
+ const projectStat = await stat(projectPath);
360
+ if (!projectStat.isDirectory())
361
+ continue;
362
+ const files = await readdir(projectPath);
363
+ const displayName = getProjectDisplayName(project);
364
+ // Initialize project entry
365
+ if (!projectMap.has(project)) {
366
+ projectMap.set(project, {
367
+ usage: {
368
+ inputTokens: 0,
369
+ cacheCreationTokens: 0,
370
+ cacheReadTokens: 0,
371
+ outputTokens: 0,
372
+ totalTokens: 0
373
+ },
374
+ sessionIds: new Set(),
375
+ displayName
376
+ });
377
+ }
378
+ for (const file of files) {
379
+ if (!file.endsWith('.jsonl'))
380
+ continue;
381
+ const filePath = join(projectPath, file);
382
+ const fileStat = await stat(filePath);
383
+ // Skip empty files
384
+ if (fileStat.size === 0)
385
+ continue;
386
+ // Skip agent sessions (they're counted in parent sessions)
387
+ const sessionId = file.replace('.jsonl', '');
388
+ if (sessionId.startsWith('agent-'))
389
+ continue;
390
+ try {
391
+ const messages = await parseJsonl(filePath);
392
+ // Skip sessions with only 1 assistant message
393
+ if (messages.length === 1 && messages[0].type === 'assistant') {
394
+ continue;
395
+ }
396
+ totalSessions++;
397
+ const projectData = projectMap.get(project);
398
+ projectData.sessionIds.add(sessionId);
399
+ // Check if this session has agent sessions
400
+ const agentDescriptions = collectAgentDescriptions(messages);
401
+ if (agentDescriptions.size > 0) {
402
+ totalAgentSessions++;
403
+ }
404
+ // Process each message
405
+ for (const message of messages) {
406
+ const tokenData = extractTokenUsage(message);
407
+ if (!tokenData)
408
+ continue;
409
+ // Aggregate by date
410
+ const messageDate = new Date(message.timestamp);
411
+ // Skip messages older than cutoff date
412
+ if (messageDate < cutoffDate)
413
+ continue;
414
+ totalMessages++;
415
+ const { usage, model } = tokenData;
416
+ // Aggregate overall
417
+ allUsages.push(usage);
418
+ allCosts.push(calculateCost(usage, model));
419
+ const dateKey = messageDate.toISOString().split('T')[0]; // YYYY-MM-DD
420
+ if (!minDate || messageDate < minDate)
421
+ minDate = messageDate;
422
+ if (!maxDate || messageDate > maxDate)
423
+ maxDate = messageDate;
424
+ if (!dailyMap.has(dateKey)) {
425
+ dailyMap.set(dateKey, {
426
+ usage: { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0, totalTokens: 0 },
427
+ sessionIds: new Set()
428
+ });
429
+ }
430
+ const dailyData = dailyMap.get(dateKey);
431
+ dailyData.usage.inputTokens += usage.inputTokens;
432
+ dailyData.usage.cacheCreationTokens += usage.cacheCreationTokens;
433
+ dailyData.usage.cacheReadTokens += usage.cacheReadTokens;
434
+ dailyData.usage.outputTokens += usage.outputTokens;
435
+ dailyData.usage.totalTokens += usage.totalTokens;
436
+ dailyData.sessionIds.add(sessionId);
437
+ // Aggregate by hour of day
438
+ const hour = messageDate.getHours();
439
+ const hourData = hourlyMap.get(hour);
440
+ hourData.usage.inputTokens += usage.inputTokens;
441
+ hourData.usage.cacheCreationTokens += usage.cacheCreationTokens;
442
+ hourData.usage.cacheReadTokens += usage.cacheReadTokens;
443
+ hourData.usage.outputTokens += usage.outputTokens;
444
+ hourData.usage.totalTokens += usage.totalTokens;
445
+ hourData.sessionIds.add(sessionId);
446
+ hourData.messageCount++;
447
+ // Aggregate by day of week
448
+ const weekday = messageDate.getDay();
449
+ const weekdayData = weekdayMap.get(weekday);
450
+ weekdayData.usage.inputTokens += usage.inputTokens;
451
+ weekdayData.usage.cacheCreationTokens += usage.cacheCreationTokens;
452
+ weekdayData.usage.cacheReadTokens += usage.cacheReadTokens;
453
+ weekdayData.usage.outputTokens += usage.outputTokens;
454
+ weekdayData.usage.totalTokens += usage.totalTokens;
455
+ weekdayData.sessionIds.add(sessionId);
456
+ weekdayData.messageCount++;
457
+ // Aggregate by project
458
+ projectData.usage.inputTokens += usage.inputTokens;
459
+ projectData.usage.cacheCreationTokens += usage.cacheCreationTokens;
460
+ projectData.usage.cacheReadTokens += usage.cacheReadTokens;
461
+ projectData.usage.outputTokens += usage.outputTokens;
462
+ projectData.usage.totalTokens += usage.totalTokens;
463
+ // Aggregate by model
464
+ if (!modelMap.has(model)) {
465
+ modelMap.set(model, {
466
+ usage: { inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0, totalTokens: 0 },
467
+ messageCount: 0
468
+ });
469
+ }
470
+ const modelData = modelMap.get(model);
471
+ modelData.usage.inputTokens += usage.inputTokens;
472
+ modelData.usage.cacheCreationTokens += usage.cacheCreationTokens;
473
+ modelData.usage.cacheReadTokens += usage.cacheReadTokens;
474
+ modelData.usage.outputTokens += usage.outputTokens;
475
+ modelData.usage.totalTokens += usage.totalTokens;
476
+ modelData.messageCount++;
477
+ // Cache stats
478
+ totalCacheCreation += usage.cacheCreationTokens;
479
+ totalCacheRead += usage.cacheReadTokens;
480
+ const cacheCreation = message.message?.usage?.cache_creation;
481
+ if (cacheCreation) {
482
+ ephemeral5mTokens += cacheCreation.ephemeral_5m_input_tokens || 0;
483
+ ephemeral1hTokens += cacheCreation.ephemeral_1h_input_tokens || 0;
484
+ }
485
+ }
486
+ // Track tool usage for all messages
487
+ const toolUseIds = new Set();
488
+ const successfulToolUseIds = new Set();
489
+ // First pass: collect all tool_use instances
490
+ for (const message of messages) {
491
+ // Skip messages older than cutoff date
492
+ const messageDate = new Date(message.timestamp);
493
+ if (messageDate < cutoffDate)
494
+ continue;
495
+ if (message.type === 'assistant' && message.message?.content && Array.isArray(message.message.content)) {
496
+ for (const item of message.message.content) {
497
+ if (item.type === 'tool_use' && item.name && item.id) {
498
+ const toolName = item.name;
499
+ if (!toolUsageMap.has(toolName)) {
500
+ toolUsageMap.set(toolName, { total: 0, successful: 0 });
501
+ }
502
+ // Only count each tool_use_id once
503
+ if (!toolUseIds.has(item.id)) {
504
+ toolUseIds.add(item.id);
505
+ const toolStats = toolUsageMap.get(toolName);
506
+ toolStats.total++;
507
+ }
508
+ }
509
+ }
510
+ }
511
+ }
512
+ // Second pass: find successful tool results
513
+ for (const message of messages) {
514
+ // Skip messages older than cutoff date
515
+ const messageDate = new Date(message.timestamp);
516
+ if (messageDate < cutoffDate)
517
+ continue;
518
+ if (message.message?.content && Array.isArray(message.message.content)) {
519
+ for (const item of message.message.content) {
520
+ if (item.type === 'tool_result' && !item.is_error && item.tool_use_id) {
521
+ // Only count each successful tool_result once
522
+ if (!successfulToolUseIds.has(item.tool_use_id)) {
523
+ successfulToolUseIds.add(item.tool_use_id);
524
+ // Find the corresponding tool_use to get the tool name
525
+ let found = false;
526
+ for (const msg of messages) {
527
+ if (msg.type === 'assistant' && msg.message?.content && Array.isArray(msg.message.content)) {
528
+ for (const toolUseItem of msg.message.content) {
529
+ if (toolUseItem.type === 'tool_use' && toolUseItem.id === item.tool_use_id && toolUseItem.name) {
530
+ const toolName = toolUseItem.name;
531
+ const toolStats = toolUsageMap.get(toolName);
532
+ if (toolStats) {
533
+ toolStats.successful++;
534
+ }
535
+ found = true;
536
+ break;
537
+ }
538
+ }
539
+ if (found)
540
+ break;
541
+ }
542
+ }
543
+ }
544
+ }
545
+ }
546
+ }
547
+ }
548
+ }
549
+ catch (error) {
550
+ console.error(`Error processing ${file}:`, error);
551
+ }
552
+ }
553
+ }
554
+ // Calculate overall totals
555
+ const totalUsage = aggregateTokenUsage(allUsages);
556
+ const totalCost = allCosts.reduce((acc, cost) => ({
557
+ inputCost: acc.inputCost + cost.inputCost,
558
+ outputCost: acc.outputCost + cost.outputCost,
559
+ cacheCreationCost: acc.cacheCreationCost + cost.cacheCreationCost,
560
+ cacheReadCost: acc.cacheReadCost + cost.cacheReadCost,
561
+ totalCost: acc.totalCost + cost.totalCost
562
+ }), { inputCost: 0, outputCost: 0, cacheCreationCost: 0, cacheReadCost: 0, totalCost: 0 });
563
+ // Calculate cache efficiency
564
+ const totalPotentialInput = totalUsage.inputTokens + totalCacheCreation + totalCacheRead;
565
+ const cacheHitRate = totalPotentialInput > 0 ? (totalCacheRead / totalPotentialInput) * 100 : 0;
566
+ // Estimate savings (cache read is ~10x cheaper than regular input for Sonnet)
567
+ const savedCost = (totalCacheRead / 1000000) * (PRICING['claude-sonnet-4-5-20250929'].input - PRICING['claude-sonnet-4-5-20250929'].cacheRead);
568
+ // Convert maps to arrays
569
+ const daily = Array.from(dailyMap.entries())
570
+ .map(([date, data]) => ({
571
+ date,
572
+ usage: data.usage,
573
+ sessionCount: data.sessionIds.size
574
+ }))
575
+ .sort((a, b) => a.date.localeCompare(b.date));
576
+ const byProject = Array.from(projectMap.entries())
577
+ .map(([project, data]) => ({
578
+ project,
579
+ displayName: data.displayName,
580
+ usage: data.usage,
581
+ sessionCount: data.sessionIds.size
582
+ }))
583
+ .sort((a, b) => b.usage.totalTokens - a.usage.totalTokens);
584
+ const byModel = Array.from(modelMap.entries())
585
+ .map(([model, data]) => ({
586
+ model,
587
+ usage: data.usage,
588
+ messageCount: data.messageCount
589
+ }))
590
+ .sort((a, b) => b.usage.totalTokens - a.usage.totalTokens);
591
+ // Calculate productivity metrics
592
+ const toolUsage = Array.from(toolUsageMap.entries())
593
+ .map(([toolName, stats]) => ({
594
+ toolName,
595
+ totalUses: stats.total,
596
+ successfulUses: stats.successful,
597
+ successRate: stats.total > 0 ? (stats.successful / stats.total) * 100 : 0
598
+ }))
599
+ .sort((a, b) => b.totalUses - a.totalUses);
600
+ // Debug: log tools with success rate > 100%
601
+ const problematicTools = toolUsage.filter(t => t.successRate > 100);
602
+ if (problematicTools.length > 0) {
603
+ console.log('⚠️ Tools with success rate > 100%:', JSON.stringify(problematicTools, null, 2));
604
+ }
605
+ const totalToolCalls = Array.from(toolUsageMap.values()).reduce((sum, stats) => sum + stats.total, 0);
606
+ const agentUsageRate = totalSessions > 0 ? (totalAgentSessions / totalSessions) * 100 : 0;
607
+ // Convert trend maps to arrays
608
+ const weekdayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
609
+ const byHour = Array.from(hourlyMap.entries())
610
+ .map(([hour, data]) => ({
611
+ hour,
612
+ sessionCount: data.sessionIds.size,
613
+ messageCount: data.messageCount,
614
+ usage: data.usage
615
+ }))
616
+ .sort((a, b) => a.hour - b.hour);
617
+ const byWeekday = Array.from(weekdayMap.entries())
618
+ .map(([weekday, data]) => ({
619
+ weekday,
620
+ weekdayName: weekdayNames[weekday],
621
+ sessionCount: data.sessionIds.size,
622
+ messageCount: data.messageCount,
623
+ usage: data.usage
624
+ }))
625
+ .sort((a, b) => a.weekday - b.weekday);
626
+ const statistics = {
627
+ overview: {
628
+ total: totalUsage,
629
+ totalSessions,
630
+ totalMessages,
631
+ dateRange: {
632
+ start: minDate?.toISOString() || new Date().toISOString(),
633
+ end: maxDate?.toISOString() || new Date().toISOString()
634
+ }
635
+ },
636
+ daily,
637
+ byProject,
638
+ byModel,
639
+ cache: {
640
+ totalCacheCreation,
641
+ totalCacheRead,
642
+ ephemeral5mTokens,
643
+ ephemeral1hTokens,
644
+ cacheHitRate,
645
+ estimatedSavings: savedCost
646
+ },
647
+ cost: totalCost,
648
+ productivity: {
649
+ toolUsage,
650
+ totalToolCalls,
651
+ agentSessions: totalAgentSessions,
652
+ totalSessions,
653
+ agentUsageRate
654
+ },
655
+ trends: {
656
+ byHour,
657
+ byWeekday
658
+ }
659
+ };
660
+ console.log('Token statistics summary:', {
661
+ totalSessions,
662
+ totalMessages,
663
+ dailyEntries: daily.length,
664
+ projectEntries: byProject.length,
665
+ modelEntries: byModel.length,
666
+ sampleDaily: daily[0]
667
+ });
668
+ return statistics;
669
+ }
670
+ catch (error) {
671
+ console.error('Error calculating token statistics:', error);
672
+ return reply.code(500).send({ error: 'Internal server error' });
673
+ }
674
+ });
224
675
  // API: Get all sessions grouped by project
225
676
  server.get('/api/sessions', async (request, reply) => {
226
677
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-viewer",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude-session-viewer": "./bin/cli.js"
@@ -44,6 +44,7 @@
44
44
  "@fastify/static": "^7.0.3",
45
45
  "@fastify/websocket": "^10.0.1",
46
46
  "@tanstack/react-query": "^5.17.19",
47
+ "chart.js": "^4.5.1",
47
48
  "chokidar": "^3.5.3",
48
49
  "date-fns": "^3.0.6",
49
50
  "fastify": "^4.25.2",
@@ -53,12 +54,14 @@
53
54
  "marked": "^11.1.1",
54
55
  "open": "^11.0.0",
55
56
  "react": "^18.2.0",
57
+ "react-chartjs-2": "^5.3.1",
56
58
  "react-dom": "^18.2.0",
57
59
  "react-router-dom": "^7.11.0",
58
60
  "react-window": "^1.8.10",
59
61
  "zustand": "^4.4.7"
60
62
  },
61
63
  "devDependencies": {
64
+ "@tailwindcss/container-queries": "^0.1.1",
62
65
  "@types/node": "^20.10.6",
63
66
  "@types/react": "^18.2.46",
64
67
  "@types/react-dom": "^18.2.18",
@@ -1 +0,0 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.hidden{display:none}.h-12{height:3rem}.h-4{height:1rem}.h-full{height:100%}.h-screen{height:100vh}.w-1{width:.25rem}.w-12{width:3rem}.w-4{width:1rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-4xl{max-width:56rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-col-resize{cursor:col-resize}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-blue-400{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.bg-blue-900\/50{background-color:#1e3a8a80}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-900\/50{background-color:#11182780}.bg-gray-900\/70{background-color:#111827b3}.bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.bg-green-900\/30{background-color:#14532d4d}.bg-green-900\/50{background-color:#14532d80}.bg-purple-700{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.bg-purple-900\/50{background-color:#581c8780}.bg-transparent{background-color:transparent}.bg-yellow-900\/30{background-color:#713f124d}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pl-14{padding-left:3.5rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-tight{line-height:1.25}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-purple-100{--tw-text-opacity: 1;color:rgb(243 232 255 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}#root{width:100%;min-height:100vh}*{scrollbar-width:thin;scrollbar-color:#4B5563 #1F2937}*::-webkit-scrollbar{width:8px;height:8px}*::-webkit-scrollbar-track{background:#1f2937}*::-webkit-scrollbar-thumb{background-color:#4b5563;border-radius:4px}*::-webkit-scrollbar-thumb:hover{background-color:#6b7280}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700\/50:hover{background-color:#37415180}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-600:hover{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}