loki-mode 6.36.0 → 6.36.2
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/README.md +2 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +107 -2
- package/dashboard/static/index.html +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/web-app/dist/assets/index-Cm-odtQC.js +66 -0
- package/web-app/dist/assets/index-SenDDIyx.css +1 -0
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +127 -12
- package/web-app/dist/assets/index-CY21Z26x.js +0 -66
- package/web-app/dist/assets/index-DW1e50zX.css +0 -1
|
@@ -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:Inter,system-ui,-apple-system,sans-serif;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:JetBrains Mono,Fira Code,Cascadia Code,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}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.terminal-scroll::-webkit-scrollbar{width:6px}.terminal-scroll::-webkit-scrollbar-track{background:transparent}.terminal-scroll::-webkit-scrollbar-thumb{background:#3d52a033;border-radius:3px}.terminal-scroll::-webkit-scrollbar-thumb:hover{background:#3d52a059}.glass{background:#fff9;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(255,255,255,.3);border-radius:16px;box-shadow:0 8px 32px #3d52a014}.glass-subtle{background:#fff6;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.2);border-radius:12px;box-shadow:0 4px 16px #3d52a00f}.pattern-circles{position:absolute;top:0;right:0;bottom:0;left:0;opacity:.04;background-image:radial-gradient(circle at 20% 30%,transparent 30%,#6C63FF 30.5%,transparent 31%),radial-gradient(circle at 80% 70%,transparent 25%,#6C63FF 25.5%,transparent 26%),radial-gradient(circle at 50% 50%,transparent 40%,#6C63FF 40.5%,transparent 41%);pointer-events:none}@keyframes phase-pulse{0%,to{opacity:1}50%{opacity:.5}}.phase-active{animation:phase-pulse 2s ease-in-out infinite}@keyframes cursor-blink{0%,to{opacity:1}50%{opacity:0}}.terminal-cursor:after{content:"";display:inline-block;width:8px;height:16px;background:#7091e6;animation:cursor-blink 1s step-end infinite;margin-left:2px;vertical-align:text-bottom}.\!visible{visibility:visible!important}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.right-0{right:0}.top-0{top:0}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.col-span-3{grid-column:span 3 / span 3}.col-span-4{grid-column:span 4 / span 4}.col-span-5{grid-column:span 5 / span 5}.col-span-6{grid-column:span 6 / span 6}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-8{margin-bottom:2rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.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}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.grid{display:grid}.h-1\.5{height:.375rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-28{height:7rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-full{height:100%}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-64{max-height:16rem}.max-h-\[400px\]{max-height:400px}.min-h-0{min-height:0px}.min-h-\[280px\]{min-height:280px}.min-h-\[300px\]{min-height:300px}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-28{width:7rem}.w-3{width:.75rem}.w-4{width:1rem}.w-56{width:14rem}.w-8{width:2rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.max-w-3xl{max-width:48rem}.max-w-\[1920px\]{max-width:1920px}.max-w-\[220px\]{max-width:220px}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,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}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0px}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.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))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-accent-product{--tw-border-opacity: 1;border-color:rgb(108 99 255 / var(--tw-border-opacity, 1))}.border-accent-product\/20{border-color:#6c63ff33}.border-accent-product\/30{border-color:#6c63ff4d}.border-danger\/20{border-color:#c45b5b33}.border-primary-wash\/30{border-color:#8697c44d}.border-primary\/20{border-color:#3d52a033}.border-slate\/20{border-color:#4f5d7533}.border-success\/20{border-color:#7fb06933}.border-surface\/50{border-color:#adbbda80}.border-warning\/20{border-color:#d4a03c33}.border-warning\/30{border-color:#d4a03c4d}.border-warning\/40{border-color:#d4a03c66}.border-white\/10{border-color:#ffffff1a}.border-white\/20{border-color:#fff3}.border-white\/30{border-color:#ffffff4d}.border-t-transparent{border-top-color:transparent}.bg-accent-product{--tw-bg-opacity: 1;background-color:rgb(108 99 255 / var(--tw-bg-opacity, 1))}.bg-accent-product\/10{background-color:#6c63ff1a}.bg-background{--tw-bg-opacity: 1;background-color:rgb(237 232 245 / var(--tw-bg-opacity, 1))}.bg-black\/30{background-color:#0000004d}.bg-black\/5{background-color:#0000000d}.bg-charcoal\/10{background-color:#2d31421a}.bg-charcoal\/5{background-color:#2d31420d}.bg-charcoal\/\[0\.03\]{background-color:#2d314208}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(196 91 91 / var(--tw-bg-opacity, 1))}.bg-danger\/10{background-color:#c45b5b1a}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(61 82 160 / var(--tw-bg-opacity, 1))}.bg-primary-light{--tw-bg-opacity: 1;background-color:rgb(112 145 230 / var(--tw-bg-opacity, 1))}.bg-primary-wash\/20{background-color:#8697c433}.bg-primary\/10{background-color:#3d52a01a}.bg-slate{--tw-bg-opacity: 1;background-color:rgb(79 93 117 / var(--tw-bg-opacity, 1))}.bg-slate\/10{background-color:#4f5d751a}.bg-slate\/30{background-color:#4f5d754d}.bg-slate\/40{background-color:#4f5d7566}.bg-success{--tw-bg-opacity: 1;background-color:rgb(127 176 105 / var(--tw-bg-opacity, 1))}.bg-success\/10{background-color:#7fb0691a}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(173 187 218 / var(--tw-bg-opacity, 1))}.bg-surface\/30{background-color:#adbbda4d}.bg-surface\/50{background-color:#adbbda80}.bg-warning{--tw-bg-opacity: 1;background-color:rgb(212 160 60 / var(--tw-bg-opacity, 1))}.bg-warning\/10{background-color:#d4a03c1a}.bg-warning\/5{background-color:#d4a03c0d}.bg-white\/30{background-color:#ffffff4d}.bg-white\/40{background-color:#fff6}.fill-charcoal{fill:#2d3142}.fill-primary{fill:#3d52a0}.p-0{padding:0}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2\.5{padding-bottom:.625rem}.pr-2{padding-right:.5rem}.pt-0{padding-top:0}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,Fira Code,Cascadia Code,monospace}.font-sans{font-family:Inter,system-ui,-apple-system,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.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}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-accent-product{--tw-text-opacity: 1;color:rgb(108 99 255 / var(--tw-text-opacity, 1))}.text-charcoal{--tw-text-opacity: 1;color:rgb(45 49 66 / var(--tw-text-opacity, 1))}.text-charcoal\/70{color:#2d3142b3}.text-charcoal\/80{color:#2d3142cc}.text-danger{--tw-text-opacity: 1;color:rgb(196 91 91 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(61 82 160 / var(--tw-text-opacity, 1))}.text-primary-light{--tw-text-opacity: 1;color:rgb(112 145 230 / var(--tw-text-opacity, 1))}.text-primary-wash{--tw-text-opacity: 1;color:rgb(134 151 196 / var(--tw-text-opacity, 1))}.text-slate{--tw-text-opacity: 1;color:rgb(79 93 117 / var(--tw-text-opacity, 1))}.text-slate\/40{color:#4f5d7566}.text-slate\/50{color:#4f5d7580}.text-slate\/60{color:#4f5d7599}.text-success{--tw-text-opacity: 1;color:rgb(127 176 105 / var(--tw-text-opacity, 1))}.text-warning{--tw-text-opacity: 1;color:rgb(212 160 60 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-glass{--tw-shadow: 0 8px 32px rgba(61,82,160,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-glass-subtle{--tw-shadow: 0 4px 16px rgba(61,82,160,.06);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 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)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.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}.duration-200{transition-duration:.2s}.duration-500{transition-duration:.5s}.placeholder\:text-primary-wash::-moz-placeholder{--tw-text-opacity: 1;color:rgb(134 151 196 / var(--tw-text-opacity, 1))}.placeholder\:text-primary-wash::placeholder{--tw-text-opacity: 1;color:rgb(134 151 196 / var(--tw-text-opacity, 1))}.placeholder\:text-primary-wash\/70::-moz-placeholder{color:#8697c4b3}.placeholder\:text-primary-wash\/70::placeholder{color:#8697c4b3}.hover\:bg-accent-product\/20:hover{background-color:#6c63ff33}.hover\:bg-accent-product\/5:hover{background-color:#6c63ff0d}.hover\:bg-accent-product\/90:hover{background-color:#6c63ffe6}.hover\:bg-danger\/20:hover{background-color:#c45b5b33}.hover\:bg-primary\/5:hover{background-color:#3d52a00d}.hover\:bg-warning\/10:hover{background-color:#d4a03c1a}.hover\:bg-white\/20:hover{background-color:#fff3}.hover\:bg-white\/30:hover{background-color:#ffffff4d}.hover\:bg-white\/40:hover{background-color:#fff6}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.hover\:text-charcoal:hover{--tw-text-opacity: 1;color:rgb(45 49 66 / var(--tw-text-opacity, 1))}.hover\:text-primary-light:hover{--tw-text-opacity: 1;color:rgb(112 145 230 / var(--tw-text-opacity, 1))}.focus\:border-accent-product\/30:focus{border-color:#6c63ff4d}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-accent-product\/20:focus{--tw-ring-color: rgb(108 99 255 / .2)}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-accent-product{--tw-text-opacity: 1;color:rgb(108 99 255 / var(--tw-text-opacity, 1))}
|
package/web-app/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Cm-odtQC.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-SenDDIyx.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="bg-background text-charcoal font-sans antialiased">
|
|
15
15
|
<div id="root"></div>
|
package/web-app/server.py
CHANGED
|
@@ -830,14 +830,67 @@ async def get_metrics() -> JSONResponse:
|
|
|
830
830
|
return JSONResponse(content=metrics)
|
|
831
831
|
|
|
832
832
|
|
|
833
|
+
def _infer_session_status(entry: Path) -> str:
|
|
834
|
+
"""Infer session status from project directory contents."""
|
|
835
|
+
# 1. Check .loki/state/session.json for explicit phase
|
|
836
|
+
state_file = entry / ".loki" / "state" / "session.json"
|
|
837
|
+
if state_file.exists():
|
|
838
|
+
try:
|
|
839
|
+
with open(state_file) as f:
|
|
840
|
+
st = json.load(f)
|
|
841
|
+
phase = st.get("phase", "")
|
|
842
|
+
if phase and phase != "idle":
|
|
843
|
+
return phase
|
|
844
|
+
except (json.JSONDecodeError, OSError):
|
|
845
|
+
pass
|
|
846
|
+
|
|
847
|
+
# 2. Check .loki/autonomy-state.json (run.sh writes this)
|
|
848
|
+
for state_name in ("autonomy-state.json", ".loki/autonomy-state.json"):
|
|
849
|
+
sf = entry / state_name
|
|
850
|
+
if sf.exists():
|
|
851
|
+
try:
|
|
852
|
+
with open(sf) as f:
|
|
853
|
+
st = json.load(f)
|
|
854
|
+
if st.get("completed") or st.get("status") == "completed":
|
|
855
|
+
return "completed"
|
|
856
|
+
if st.get("status"):
|
|
857
|
+
return st["status"]
|
|
858
|
+
except (json.JSONDecodeError, OSError):
|
|
859
|
+
pass
|
|
860
|
+
|
|
861
|
+
# 3. Infer from file contents
|
|
862
|
+
files = set()
|
|
863
|
+
try:
|
|
864
|
+
files = {f.name for f in entry.iterdir() if not f.name.startswith(".")}
|
|
865
|
+
except OSError:
|
|
866
|
+
pass
|
|
867
|
+
|
|
868
|
+
source_extensions = {".js", ".ts", ".tsx", ".py", ".html", ".css", ".go", ".rs", ".java", ".rb"}
|
|
869
|
+
has_source = any(
|
|
870
|
+
(entry / f).suffix in source_extensions
|
|
871
|
+
for f in files
|
|
872
|
+
if (entry / f).is_file()
|
|
873
|
+
)
|
|
874
|
+
has_prd = "PRD.md" in files or "prd.md" in files
|
|
875
|
+
|
|
876
|
+
if has_source:
|
|
877
|
+
return "completed"
|
|
878
|
+
if has_prd and len(files) <= 2:
|
|
879
|
+
return "started"
|
|
880
|
+
if has_prd:
|
|
881
|
+
return "in_progress"
|
|
882
|
+
|
|
883
|
+
return "empty"
|
|
884
|
+
|
|
885
|
+
|
|
833
886
|
@app.get("/api/sessions/history")
|
|
834
887
|
async def get_sessions_history() -> JSONResponse:
|
|
835
|
-
"""Return list of past loki sessions from
|
|
888
|
+
"""Return list of past loki sessions from ~/purple-lab-projects/ etc."""
|
|
836
889
|
history: list[dict] = []
|
|
837
890
|
search_dirs = [
|
|
891
|
+
Path.home() / "purple-lab-projects",
|
|
838
892
|
Path.home() / ".loki-sessions",
|
|
839
893
|
Path.home() / ".loki" / "sessions",
|
|
840
|
-
Path.home() / "purple-lab-projects",
|
|
841
894
|
]
|
|
842
895
|
for base_dir in search_dirs:
|
|
843
896
|
if not base_dir.is_dir():
|
|
@@ -850,7 +903,7 @@ async def get_sessions_history() -> JSONResponse:
|
|
|
850
903
|
"path": str(entry),
|
|
851
904
|
"date": "",
|
|
852
905
|
"prd_snippet": "",
|
|
853
|
-
"status":
|
|
906
|
+
"status": _infer_session_status(entry),
|
|
854
907
|
}
|
|
855
908
|
# Read timestamp from directory mtime
|
|
856
909
|
try:
|
|
@@ -869,21 +922,83 @@ async def get_sessions_history() -> JSONResponse:
|
|
|
869
922
|
except OSError:
|
|
870
923
|
pass
|
|
871
924
|
break
|
|
872
|
-
#
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
925
|
+
# Count project files for progress indication
|
|
926
|
+
try:
|
|
927
|
+
file_count = sum(1 for f in entry.rglob("*") if f.is_file()
|
|
928
|
+
and ".git" not in f.parts and "node_modules" not in f.parts
|
|
929
|
+
and "__pycache__" not in f.parts)
|
|
930
|
+
session_info["file_count"] = file_count
|
|
931
|
+
except OSError:
|
|
932
|
+
session_info["file_count"] = 0
|
|
933
|
+
|
|
881
934
|
history.append(session_info)
|
|
882
935
|
if history:
|
|
883
936
|
break # Use first directory that has entries
|
|
884
937
|
return JSONResponse(content=history)
|
|
885
938
|
|
|
886
939
|
|
|
940
|
+
@app.get("/api/sessions/{session_id}")
|
|
941
|
+
async def get_session_detail(session_id: str) -> JSONResponse:
|
|
942
|
+
"""Get details of a past session for read-only viewing."""
|
|
943
|
+
import re
|
|
944
|
+
# Validate session_id format (prevent path traversal)
|
|
945
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", session_id):
|
|
946
|
+
return JSONResponse(status_code=400, content={"error": "Invalid session ID"})
|
|
947
|
+
|
|
948
|
+
search_dirs = [
|
|
949
|
+
Path.home() / "purple-lab-projects",
|
|
950
|
+
Path.home() / ".loki-sessions",
|
|
951
|
+
Path.home() / ".loki" / "sessions",
|
|
952
|
+
]
|
|
953
|
+
target: Optional[Path] = None
|
|
954
|
+
for base_dir in search_dirs:
|
|
955
|
+
candidate = base_dir / session_id
|
|
956
|
+
if candidate.is_dir():
|
|
957
|
+
target = candidate
|
|
958
|
+
break
|
|
959
|
+
|
|
960
|
+
if target is None:
|
|
961
|
+
return JSONResponse(status_code=404, content={"error": "Session not found"})
|
|
962
|
+
|
|
963
|
+
# Read PRD
|
|
964
|
+
prd_content = ""
|
|
965
|
+
for prd_name in ("PRD.md", "prd.md", ".loki/prd.md"):
|
|
966
|
+
prd_file = target / prd_name
|
|
967
|
+
if prd_file.exists():
|
|
968
|
+
try:
|
|
969
|
+
prd_content = prd_file.read_text(errors="replace")
|
|
970
|
+
except OSError:
|
|
971
|
+
pass
|
|
972
|
+
break
|
|
973
|
+
|
|
974
|
+
# Build file tree
|
|
975
|
+
files = _build_file_tree(target)
|
|
976
|
+
|
|
977
|
+
# Read logs if available
|
|
978
|
+
log_lines: list[str] = []
|
|
979
|
+
for log_name in (".loki/logs/session.log", ".loki/session.log", "loki.log"):
|
|
980
|
+
log_file = target / log_name
|
|
981
|
+
if log_file.exists():
|
|
982
|
+
try:
|
|
983
|
+
text = log_file.read_text(errors="replace")
|
|
984
|
+
log_lines = text.splitlines()[-200:]
|
|
985
|
+
except OSError:
|
|
986
|
+
pass
|
|
987
|
+
break
|
|
988
|
+
|
|
989
|
+
# Status
|
|
990
|
+
status = _infer_session_status(target)
|
|
991
|
+
|
|
992
|
+
return JSONResponse(content={
|
|
993
|
+
"id": session_id,
|
|
994
|
+
"path": str(target),
|
|
995
|
+
"status": status,
|
|
996
|
+
"prd": prd_content,
|
|
997
|
+
"files": files,
|
|
998
|
+
"logs": log_lines,
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
|
|
887
1002
|
@app.post("/api/session/onboard")
|
|
888
1003
|
async def onboard_session(req: OnboardRequest) -> JSONResponse:
|
|
889
1004
|
"""Run loki onboard on a path and return CLAUDE.md content."""
|