claude-session-viewer 0.3.4 → 0.3.6

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}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-1\/2{left:50%}.right-0{right:0}.top-0{top:0}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.-mt-px{margin-top:-1px}.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-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.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-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.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-16{width:4rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-4\/5{width:80%}.w-5{width:1.25rem}.w-5\/6{width:83.333333%}.w-64{width:16rem}.w-8{width:2rem}.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}.-translate-x-1\/2{--tw-translate-x: -50%;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))}.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))}.transform{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}.cursor-pointer{cursor:pointer}.resize{resize:both}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,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}.items-baseline{align-items:baseline}.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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * 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-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{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-4{border-width:4px}.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-t{border-top-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}.border-t-gray-900{--tw-border-opacity: 1;border-top-color:rgb(17 24 39 / var(--tw-border-opacity, 1))}.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}.pt-6{padding-top:1.5rem}.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))}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.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-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.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))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.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-2{grid-template-columns:repeat(2,minmax(0,1fr))}.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-KEbXAXOS.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DvK33tag.css">
8
+ <script type="module" crossorigin src="/assets/index-Csvqk3mE.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DOk7moPK.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -30,7 +30,7 @@ export function getProjectPath(projectsDir, projectName) {
30
30
  * Remove user's home directory prefix from project directory name
31
31
  * e.g., "-Users-hanyeol-Projects-foo" → "Projects-foo"
32
32
  */
33
- export function getProjectDisplayName(projectDirName) {
33
+ export function getProjectName(projectDirName) {
34
34
  const userHomePath = homedir().split('/').filter(Boolean).join('-');
35
35
  const prefix = `-${userHomePath}-`;
36
36
  if (projectDirName.startsWith(prefix)) {
@@ -1,6 +1,6 @@
1
1
  import { stat } from 'fs/promises';
2
2
  import { join } from 'path';
3
- import { listProjects, getProjectDisplayName } from './repository.js';
3
+ import { listProjects, getProjectName } from './repository.js';
4
4
  import { getProjectSessions, sortSessionsByTimestamp } from '../sessions/service.js';
5
5
  /**
6
6
  * Project service
@@ -25,10 +25,10 @@ export async function getAllProjectsWithSessions(projectsDir) {
25
25
  const sessions = await getProjectSessions(projectPath);
26
26
  if (sessions.length > 0) {
27
27
  sortSessionsByTimestamp(sessions);
28
- const displayName = getProjectDisplayName(project);
28
+ const name = getProjectName(project);
29
29
  projectGroups.push({
30
- name: project,
31
- displayName,
30
+ id: project,
31
+ name,
32
32
  sessionCount: sessions.length,
33
33
  lastActivity: sessions[0].timestamp,
34
34
  sessions
@@ -3,7 +3,7 @@ import { listSessionFiles, readSessionFile } from './repository.js';
3
3
  import { shouldSkipSession, isAgentSession, isEmptyFile } from './filters.js';
4
4
  import { extractSessionTitle } from './title.js';
5
5
  import { collectAgentDescriptions, attachAgentSessionsToParent } from './agents.js';
6
- import { getProjectDisplayName } from '../projects/repository.js';
6
+ import { getProjectName } from '../projects/repository.js';
7
7
  /**
8
8
  * Session service
9
9
  * Orchestrates session loading, filtering, and organization
@@ -21,7 +21,8 @@ export async function getProjectSessions(projectPath) {
21
21
  const sessionFiles = await listSessionFiles(projectPath);
22
22
  const allSessions = [];
23
23
  const agentSessionsMap = new Map();
24
- const projectName = getProjectDisplayName(projectPath.split('/').pop() || 'unknown');
24
+ const projectId = projectPath.split('/').pop() || 'unknown';
25
+ const projectName = getProjectName(projectId);
25
26
  // First pass: load all sessions
26
27
  for (const { filename, size, path } of sessionFiles) {
27
28
  if (isEmptyFile(size))
@@ -33,7 +34,8 @@ export async function getProjectSessions(projectPath) {
33
34
  continue;
34
35
  const session = {
35
36
  id: sessionId,
36
- project: projectName,
37
+ projectId,
38
+ projectName,
37
39
  timestamp,
38
40
  messages,
39
41
  messageCount: messages.length,
@@ -61,15 +63,17 @@ export async function getProjectSessions(projectPath) {
61
63
  /**
62
64
  * Load agent sessions from files
63
65
  */
64
- export async function loadAgentSessionsFromFiles(projectPath, projectName, agentDescriptions) {
66
+ export async function loadAgentSessionsFromFiles(projectPath, projectId, agentDescriptions) {
65
67
  const agentSessions = [];
68
+ const projectName = getProjectName(projectId);
66
69
  for (const [agentSessionId, description] of agentDescriptions) {
67
70
  const agentFile = join(projectPath, `${agentSessionId}.jsonl`);
68
71
  try {
69
72
  const { messages, timestamp } = await readSessionFile(agentFile);
70
73
  agentSessions.push({
71
74
  id: agentSessionId,
72
- project: projectName,
75
+ projectId,
76
+ projectName,
73
77
  timestamp,
74
78
  messages,
75
79
  messageCount: messages.length,
@@ -0,0 +1,127 @@
1
+ import { readdir, stat } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { CLAUDE_DIR } from '../constants.js';
4
+ import { parseJsonl } from '../utils/jsonl.js';
5
+ import { isAgentSession, isEmptyFile, shouldSkipSession } from '../claude/sessions/filters.js';
6
+ import { calculateSessionWindows, getActiveWindow, loadPlanConfig } from '../utils/sessionWindows.js';
7
+ import { PLAN_LIMITS } from '../claude/config.js';
8
+ /**
9
+ * Session window routes
10
+ */
11
+ export async function registerSessionWindowRoutes(server) {
12
+ /**
13
+ * GET /api/session-windows
14
+ * Returns all 5-hour session windows with token usage
15
+ */
16
+ server.get('/api/session-windows', async (request, reply) => {
17
+ try {
18
+ const projectsDir = join(CLAUDE_DIR, 'projects');
19
+ const projects = await readdir(projectsDir);
20
+ // Parse days parameter
21
+ const daysParam = request.query.days || '8'; // Default 8 days for P90 calculation
22
+ const cutoffDate = new Date();
23
+ if (daysParam !== 'all') {
24
+ const days = parseInt(daysParam, 10);
25
+ cutoffDate.setDate(cutoffDate.getDate() - days);
26
+ cutoffDate.setHours(0, 0, 0, 0);
27
+ }
28
+ else {
29
+ cutoffDate.setFullYear(2000, 0, 1);
30
+ }
31
+ // Collect all session data
32
+ const sessionsData = [];
33
+ for (const project of projects) {
34
+ const projectPath = join(projectsDir, project);
35
+ const projectStat = await stat(projectPath);
36
+ if (!projectStat.isDirectory())
37
+ continue;
38
+ const files = await readdir(projectPath);
39
+ for (const file of files) {
40
+ if (!file.endsWith('.jsonl'))
41
+ continue;
42
+ const filePath = join(projectPath, file);
43
+ const fileStat = await stat(filePath);
44
+ if (isEmptyFile(fileStat.size))
45
+ continue;
46
+ const sessionId = file.replace('.jsonl', '');
47
+ if (isAgentSession(sessionId))
48
+ continue;
49
+ try {
50
+ const messages = await parseJsonl(filePath);
51
+ if (shouldSkipSession(messages))
52
+ continue;
53
+ // Filter messages by date
54
+ const filteredMessages = messages.filter((msg) => {
55
+ if (!msg.timestamp)
56
+ return false;
57
+ const msgDate = new Date(msg.timestamp);
58
+ return msgDate >= cutoffDate;
59
+ });
60
+ if (filteredMessages.length > 0) {
61
+ sessionsData.push({
62
+ sessionId,
63
+ messages: filteredMessages
64
+ });
65
+ }
66
+ }
67
+ catch (error) {
68
+ console.error(`Error processing ${file}:`, error);
69
+ }
70
+ }
71
+ }
72
+ // Calculate initial windows with default plan for detection
73
+ const initialWindows = calculateSessionWindows(sessionsData, {
74
+ name: 'Custom',
75
+ limits: PLAN_LIMITS.Custom,
76
+ autoDetected: false
77
+ });
78
+ // Auto-detect plan based on windows
79
+ const planConfig = loadPlanConfig(initialWindows);
80
+ // Recalculate windows with detected plan
81
+ const windows = calculateSessionWindows(sessionsData, planConfig);
82
+ // Get active window
83
+ const activeWindow = getActiveWindow(windows);
84
+ const response = {
85
+ windows,
86
+ activeWindow,
87
+ planConfig
88
+ };
89
+ return response;
90
+ }
91
+ catch (error) {
92
+ console.error('Error calculating session windows:', error);
93
+ return reply.code(500).send({ error: 'Internal server error' });
94
+ }
95
+ });
96
+ /**
97
+ * GET /api/session-windows/active
98
+ * Returns only the currently active session window
99
+ */
100
+ server.get('/api/session-windows/active', async (request, reply) => {
101
+ try {
102
+ // Reuse the main endpoint logic but return only active window
103
+ const fullResponse = await server.inject({
104
+ method: 'GET',
105
+ url: '/api/session-windows?days=1' // Only check last day for performance
106
+ });
107
+ if (fullResponse.statusCode !== 200) {
108
+ return reply.code(fullResponse.statusCode).send(fullResponse.json());
109
+ }
110
+ const data = fullResponse.json();
111
+ if (!data.activeWindow) {
112
+ return {
113
+ activeWindow: null,
114
+ planConfig: data.planConfig
115
+ };
116
+ }
117
+ return {
118
+ activeWindow: data.activeWindow,
119
+ planConfig: data.planConfig
120
+ };
121
+ }
122
+ catch (error) {
123
+ console.error('Error getting active session window:', error);
124
+ return reply.code(500).send({ error: 'Internal server error' });
125
+ }
126
+ });
127
+ }
@@ -3,7 +3,7 @@ import { join } from 'path';
3
3
  import { CLAUDE_DIR } from '../constants.js';
4
4
  import { parseJsonl } from '../utils/jsonl.js';
5
5
  import { getAllProjectsWithSessions } from '../claude/projects/service.js';
6
- import { getProjectDisplayName } from '../claude/projects/repository.js';
6
+ import { getProjectName } from '../claude/projects/repository.js';
7
7
  import { loadAgentSessionsFromFiles } from '../claude/sessions/service.js';
8
8
  import { collectAgentDescriptions, injectAgentIdsIntoMessages, findAgentTitleFromParentMessages } from '../claude/sessions/agents.js';
9
9
  import { isAgentSession } from '../claude/sessions/filters.js';
@@ -37,7 +37,7 @@ export async function registerSessionRoutes(server) {
37
37
  try {
38
38
  const messages = await parseJsonl(sessionFile);
39
39
  const fileStat = await stat(sessionFile);
40
- const projectName = getProjectDisplayName(project);
40
+ const projectName = project;
41
41
  let title = extractSessionTitle(messages);
42
42
  // For agent sessions, find description from parent
43
43
  if (isAgent) {
@@ -64,14 +64,15 @@ export async function registerSessionRoutes(server) {
64
64
  if (!isAgent) {
65
65
  const agentDescriptions = collectAgentDescriptions(messages);
66
66
  if (agentDescriptions.size > 0) {
67
- agentSessions = await loadAgentSessionsFromFiles(projectPath, projectName, agentDescriptions);
67
+ agentSessions = await loadAgentSessionsFromFiles(projectPath, project, agentDescriptions);
68
68
  }
69
69
  }
70
70
  const messagesWithAgentIds = injectAgentIdsIntoMessages(messages);
71
71
  return {
72
72
  session: {
73
73
  id,
74
- project: projectName,
74
+ projectId: projectName,
75
+ projectName: getProjectName(projectName),
75
76
  timestamp: fileStat.mtime.toISOString(),
76
77
  messages: messagesWithAgentIds,
77
78
  messageCount: messages.length,