hammerkit 2.0.0-next.3 → 2.0.0-next.5

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,2 @@
1
+ /*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:block))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-leading:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-950:oklch(27.9% .077 45.635);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-950:oklch(26.2% .051 172.552);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-950:oklch(29.3% .066 243.157);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;scrollbar-gutter:stable}@media (prefers-color-scheme:dark){html{--lightningcss-light: ;--lightningcss-dark:initial}}body{background-color:var(--color-stone-50);color:var(--color-stone-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){body{background-color:var(--color-stone-950);color:var(--color-stone-100)}}}@layer components{.link{color:var(--color-emerald-700);text-underline-offset:2px}@media (hover:hover){.link:hover{text-decoration-line:underline}}@media (prefers-color-scheme:dark){.link{color:var(--color-emerald-400)}}.log{border-radius:var(--radius-lg);background-color:var(--color-stone-950);padding:calc(var(--spacing) * 3);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);white-space:pre;color:var(--color-stone-200);overflow-x:auto}@media (prefers-color-scheme:dark){.log{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-stone-800)}}}@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-y-0{inset-block:0}.right-0{right:0}.left-0{left:0}.z-20{z-index:20}.z-50{z-index:50}.order-last{order:9999}.col-span-2{grid-column:span 2/span 2}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.mt-1{margin-top:var(--spacing)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:var(--spacing)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-1{margin-left:var(--spacing)}.ml-auto{margin-left:auto}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.max-h-80{max-height:calc(var(--spacing) * 80)}.min-h-screen{min-height:100vh}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-10{width:calc(var(--spacing) * 10)}.w-64{width:calc(var(--spacing) * 64)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-72{max-width:calc(var(--spacing) * 72)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-48{min-width:calc(var(--spacing) * 48)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-center{transform-origin:50%}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[9rem_1fr\]{grid-template-columns:9rem 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[auto_auto\]{grid-template-columns:auto auto}.grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-stone-100>:not(:last-child)){border-color:var(--color-stone-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-amber-500\/50{border-color:#f99c0080}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/50{border-color:color-mix(in oklab, var(--color-amber-500) 50%, transparent)}}.border-amber-600\/40{border-color:#dd740066}@supports (color:color-mix(in lab, red, red)){.border-amber-600\/40{border-color:color-mix(in oklab, var(--color-amber-600) 40%, transparent)}}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-600\/40{border-color:#00976766}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/40{border-color:color-mix(in oklab, var(--color-emerald-600) 40%, transparent)}}.border-emerald-600\/50{border-color:#00976780}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/50{border-color:color-mix(in oklab, var(--color-emerald-600) 50%, transparent)}}.border-emerald-700{border-color:var(--color-emerald-700)}.border-red-300{border-color:var(--color-red-300)}.border-red-600\/40{border-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.border-red-600\/40{border-color:color-mix(in oklab, var(--color-red-600) 40%, transparent)}}.border-sky-600\/40{border-color:#0084cc66}@supports (color:color-mix(in lab, red, red)){.border-sky-600\/40{border-color:color-mix(in oklab, var(--color-sky-600) 40%, transparent)}}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-transparent{border-color:#0000}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-600\/30{background-color:#0097674d}@supports (color:color-mix(in lab, red, red)){.bg-emerald-600\/30{background-color:color-mix(in oklab, var(--color-emerald-600) 30%, transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-50\/60{background-color:#fafaf999}@supports (color:color-mix(in lab, red, red)){.bg-stone-50\/60{background-color:color-mix(in oklab, var(--color-stone-50) 60%, transparent)}}.bg-stone-200{background-color:var(--color-stone-200)}.bg-white{background-color:var(--color-white)}.fill-current{fill:currentColor}.fill-none{fill:none}.stroke-current{stroke:currentColor}.stroke-none{stroke:none}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.pt-1{padding-top:var(--spacing)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-9{padding-left:calc(var(--spacing) * 9)}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.7rem\]{font-size:.7rem}.text-\[0\.85em\]{font-size:.85em}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-sky-400{color:var(--color-sky-400)}.text-sky-700{color:var(--color-sky-700)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-900{color:var(--color-stone-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, )}.underline{text-decoration-line:underline}.decoration-stone-400\/40{text-decoration-color:#a6a09b66}@supports (color:color-mix(in lab, red, red)){.decoration-stone-400\/40{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent)}}.decoration-stone-400\/70{text-decoration-color:#a6a09bb3}@supports (color:color-mix(in lab, red, red)){.decoration-stone-400\/70{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent)}}.decoration-dotted{text-decoration-style:dotted}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-emerald-600{accent-color:var(--color-emerald-600)}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), 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-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-stone-400:hover{border-color:var(--color-stone-400)}.hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.hover\:bg-emerald-50:hover{background-color:var(--color-emerald-50)}.hover\:bg-emerald-700:hover{background-color:var(--color-emerald-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-stone-50:hover{background-color:var(--color-stone-50)}.hover\:bg-stone-100:hover{background-color:var(--color-stone-100)}.hover\:text-stone-900:hover{color:var(--color-stone-900)}.hover\:underline:hover{text-decoration-line:underline}.hover\:decoration-current:hover{text-decoration-color:currentColor}}.focus-visible\:rounded-sm:focus-visible{border-radius:var(--radius-sm)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset, ) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-emerald-500:focus-visible{--tw-ring-color:var(--color-emerald-500)}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:ml-auto{margin-left:auto}.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:max-w-xs{max-width:var(--container-xs)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:order-none{order:0}.md\:w-auto{width:auto}.md\:flex-1{flex:1}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}}@media (prefers-color-scheme:dark){:where(.dark\:divide-stone-800>:not(:last-child)){border-color:var(--color-stone-800)}.dark\:border-emerald-500{border-color:var(--color-emerald-500)}.dark\:border-emerald-700{border-color:var(--color-emerald-700)}.dark\:border-red-900{border-color:var(--color-red-900)}.dark\:border-stone-700{border-color:var(--color-stone-700)}.dark\:border-stone-800{border-color:var(--color-stone-800)}.dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-emerald-400\/30{background-color:#00d2944d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-emerald-400\/30{background-color:color-mix(in oklab, var(--color-emerald-400) 30%, transparent)}}.dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark\:bg-red-950{background-color:var(--color-red-950)}.dark\:bg-sky-950{background-color:var(--color-sky-950)}.dark\:bg-stone-800{background-color:var(--color-stone-800)}.dark\:bg-stone-900{background-color:var(--color-stone-900)}.dark\:bg-stone-950\/30{background-color:#0c0a094d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-950\/30{background-color:color-mix(in oklab, var(--color-stone-950) 30%, transparent)}}.dark\:bg-stone-950\/40{background-color:#0c0a0966}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-950\/40{background-color:color-mix(in oklab, var(--color-stone-950) 40%, transparent)}}.dark\:text-amber-300{color:var(--color-amber-300)}.dark\:text-amber-400{color:var(--color-amber-400)}.dark\:text-emerald-200{color:var(--color-emerald-200)}.dark\:text-emerald-300{color:var(--color-emerald-300)}.dark\:text-emerald-400{color:var(--color-emerald-400)}.dark\:text-red-300{color:var(--color-red-300)}.dark\:text-red-400{color:var(--color-red-400)}.dark\:text-sky-300{color:var(--color-sky-300)}.dark\:text-stone-200{color:var(--color-stone-200)}.dark\:text-stone-300{color:var(--color-stone-300)}.dark\:text-stone-400{color:var(--color-stone-400)}.dark\:text-stone-500{color:var(--color-stone-500)}.dark\:text-stone-950{color:var(--color-stone-950)}.dark\:text-white{color:var(--color-white)}.dark\:decoration-stone-500\/50{text-decoration-color:#79716b80}@supports (color:color-mix(in lab, red, red)){.dark\:decoration-stone-500\/50{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent)}}.dark\:decoration-stone-500\/70{text-decoration-color:#79716bb3}@supports (color:color-mix(in lab, red, red)){.dark\:decoration-stone-500\/70{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent)}}@media (hover:hover){.dark\:hover\:border-stone-500:hover{border-color:var(--color-stone-500)}.dark\:hover\:bg-emerald-400:hover{background-color:var(--color-emerald-400)}.dark\:hover\:bg-emerald-950:hover{background-color:var(--color-emerald-950)}.dark\:hover\:bg-red-950:hover{background-color:var(--color-red-950)}.dark\:hover\:bg-stone-700:hover{background-color:var(--color-stone-700)}.dark\:hover\:bg-stone-800:hover{background-color:var(--color-stone-800)}.dark\:hover\:text-white:hover{color:var(--color-white)}}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <meta name="robots" content="noindex" />
7
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
7
+ <link rel="icon" href="/logo.png" type="image/png" />
8
8
  <title>hammerkit</title>
9
- <script type="module" crossorigin src="/assets/index-zPXa4klZ.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-CjnKm5-3.css">
9
+ <script type="module" crossorigin src="/assets/index-D51NA6-h.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-mmvC6u9O.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
Binary file
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensurePersistentData = exports.packSource = exports.restoreLiveFiles = exports.syncLiveFiles = exports.SYNC_FILES = exports.getStateDirsInUse = exports.REMOVE_OTHER_STATES = exports.execInPod = exports.clearClaim = exports.getPodForPersistence = void 0;
3
+ exports.ensurePersistentData = exports.packSource = exports.clearStateFiles = exports.restoreLiveFiles = exports.syncLiveFiles = exports.SYNC_FILES = exports.getStateDirsInUse = exports.REMOVE_OTHER_STATES = exports.execInPod = exports.clearClaim = exports.getPodForPersistence = void 0;
4
4
  const volumes_1 = require("./volumes");
5
5
  const apply_1 = require("./apply");
6
6
  const path_1 = require("path");
@@ -282,6 +282,31 @@ exports.restoreLiveFiles = restoreLiveFiles;
282
282
  // Returns whether the upload is needed.
283
283
  async function prepareStateFiles(instance, env, work, pod, dir, inUse) {
284
284
  const targetPath = `/dev/hammerkit/${work.id()}`;
285
+ await removeOtherStates(instance, env, pod, targetPath, dir, inUse);
286
+ if (inUse.has(dir) && !(await execInPod(instance, env, pod, ['test', '-e', `${targetPath}/${dir}/${UPLOADED}`]))) {
287
+ work.status.write('info', `the files of ${work.name} are there already`);
288
+ return false;
289
+ }
290
+ await clearStateDir(instance, env, pod, `${targetPath}/${dir}`);
291
+ return true;
292
+ }
293
+ // what a failed upload, restore or task left: tar does not remove files the commit does not have
294
+ async function clearStateDir(instance, env, pod, path) {
295
+ const cleared = await execInPod(instance, env, pod, ['rm', '-rf', path]);
296
+ if (cleared) {
297
+ throw new Error(`unable to clear ${path}: ${cleared}`);
298
+ }
299
+ }
300
+ // Before a restore of a service run into the folder `dir` of the claim of `work` (in its upload
301
+ // pod `pod`): the folders of the other states go but those of `inUse`, which pods of the run
302
+ // mount, and `dir` is emptied. A claim that took only restores holds one state, as after a Job.
303
+ async function clearStateFiles(instance, env, work, pod, dir, inUse) {
304
+ const targetPath = `/dev/hammerkit/${work.id()}`;
305
+ await removeOtherStates(instance, env, pod, targetPath, dir, inUse);
306
+ await clearStateDir(instance, env, pod, `${targetPath}/${dir}`);
307
+ }
308
+ exports.clearStateFiles = clearStateFiles;
309
+ async function removeOtherStates(instance, env, pod, targetPath, dir, inUse) {
285
310
  const removed = await execInPod(instance, env, pod, [
286
311
  'sh',
287
312
  '-c',
@@ -294,16 +319,6 @@ async function prepareStateFiles(instance, env, work, pod, dir, inUse) {
294
319
  if (removed) {
295
320
  throw new Error(`unable to remove the files of other commits in ${targetPath}: ${removed}`);
296
321
  }
297
- if (inUse.has(dir) && !(await execInPod(instance, env, pod, ['test', '-e', `${targetPath}/${dir}/${UPLOADED}`]))) {
298
- work.status.write('info', `the files of ${work.name} are there already`);
299
- return false;
300
- }
301
- // what a failed upload or task left: tar does not remove files the commit does not have
302
- const cleared = await execInPod(instance, env, pod, ['rm', '-rf', `${targetPath}/${dir}`]);
303
- if (cleared) {
304
- throw new Error(`unable to clear ${targetPath}/${dir}: ${cleared}`);
305
- }
306
- return true;
307
322
  }
308
323
  // The archive that uploads a source into the claim: its file or directory, at its place in
309
324
  // the claim (see getClaimSubPath) instead of under its own name
@@ -99,11 +99,15 @@ async function annotate(instance, namespace, name, annotations) {
99
99
  }
100
100
  }
101
101
  // The state key of the outputs whose folder is whole in the claim of a task of a service run
102
- // (see getStateFilesDir), null when it names none. The run's namespace is its own, and only
103
- // the run writes to the claim: it forgets the state before it writes, records it after.
102
+ // (see getStateFilesDir), null when it names none (also while the claim is being removed),
103
+ // undefined when there is no claim. The run's namespace is its own, and only the run writes
104
+ // to the claim: it forgets the state before it writes, records it after.
104
105
  async function rememberedClaimState(instance, kubernetes, name) {
105
106
  const claim = await readClaim(instance, kubernetes.namespace, name);
106
- if (!claim || claim.metadata?.deletionTimestamp) {
107
+ if (!claim) {
108
+ return undefined;
109
+ }
110
+ if (claim.metadata?.deletionTimestamp) {
107
111
  return null;
108
112
  }
109
113
  return claim.metadata?.annotations?.[exports.STATE_ANNOTATION] ?? null;
@@ -16,7 +16,9 @@ stateDir) {
16
16
  exports.getRestoreTarget = getRestoreTarget;
17
17
  async function restoreKubernetesData(work, kubernetes, instance, environment, path,
18
18
  // the kept claim of the work holds what an earlier run left, which goes first; on a
19
- // service run, the outputs go into the folder of `stateKey` (see getStateFilesDir)
19
+ // service run, the outputs go into the folder of `stateKey` (see getStateFilesDir);
20
+ // `otherStates`: the folders of the other states go but these (the ones pods of the run
21
+ // mount), and the folder of `stateKey` is emptied first
20
22
  options = {}) {
21
23
  const persistence = await (0, volumes_1.getKubernetesPersistence)(work, kubernetes, options.stateKey);
22
24
  const stateDir = kubernetes.server?.serviceRun ? (0, volumes_1.getItemFilesDir)(work, options.stateKey) : undefined;
@@ -24,6 +26,9 @@ options = {}) {
24
26
  if (options.reset) {
25
27
  await (0, ensure_persistent_data_1.clearClaim)(instance, kubernetes, work, name);
26
28
  }
29
+ if (stateDir && options.otherStates) {
30
+ await (0, ensure_persistent_data_1.clearStateFiles)(instance, kubernetes, work, name, stateDir, options.otherStates);
31
+ }
27
32
  for (const generatedArchive of (0, event_cache_1.getArchivePaths)(work.data, path)) {
28
33
  if (!(await environment.file.exists(generatedArchive.filename))) {
29
34
  continue;
@@ -190,8 +190,13 @@ function kubernetesTaskRuntime(task, kubernetes) {
190
190
  }
191
191
  if (remembers && !kept) {
192
192
  // the folder of the state is written now: until it is whole, the claim names no state
193
+ // (a restore without a state key says none, and the next run uploads again)
193
194
  await (0, kept_claims_1.forgetClaimState)(instance, kubernetes, claim);
194
- await (0, restore_kubernetes_data_1.restoreKubernetesData)(task, kubernetes, instance, environment, path, { stateKey });
195
+ await (0, restore_kubernetes_data_1.restoreKubernetesData)(task, kubernetes, instance, environment, path, {
196
+ stateKey,
197
+ // the folders of other states no pod of the run mounts go, as before a Job
198
+ ...(stateKey ? { otherStates: await (0, ensure_persistent_data_1.getStateDirsInUse)(instance, kubernetes.namespace, claim) } : {}),
199
+ });
195
200
  if (stateKey) {
196
201
  await (0, kept_claims_1.recordClaimState)(instance, kubernetes, claim, stateKey);
197
202
  }
@@ -488,9 +493,11 @@ function kubernetesTaskRuntime(task, kubernetes) {
488
493
  return (0, kept_claims_1.useClaimState)(instance, kubernetes, claim);
489
494
  }
490
495
  if (remembers) {
491
- // a service run that sleeps loses its Jobs but keeps its claims, the claim says
496
+ // a service run that sleeps loses its Jobs but keeps its claims, the claim says. One
497
+ // that names no state is being written (or was, and that failed): the folder of the
498
+ // state of an earlier Job may be gone. Only a task without a claim goes by its Jobs.
492
499
  const remembered = await (0, kept_claims_1.rememberedClaimState)(instance, kubernetes, claim);
493
- if (remembered) {
500
+ if (remembered !== undefined) {
494
501
  return remembered;
495
502
  }
496
503
  }
@@ -519,16 +519,25 @@ function registerWorkCommands(remote, context) {
519
519
  .command('tasks [server]')
520
520
  .description('show what the past runs of each task took, over the runs the server keeps: how often it ran, failed and came from the cache, the median and 90th percentile of its time, and how long its runs waited for a place')
521
521
  .addOption(new commander_1.Option('--mine', 'only your runs'))
522
+ .addOption(new commander_1.Option('--since <when>', 'only the runs created since then: back from now, such as 24h or 7d, or a time'))
523
+ .addOption(new commander_1.Option('--until <when>', 'only the runs created until then: back from now, or a time'))
522
524
  .addOption(new commander_1.Option('--json', 'print it as the server answers it'))
523
525
  .action(guarded('Remote tasks was not successful', async (name, options) => {
524
526
  const { name: serverName, server, token } = await connect(name);
525
- const { tasks } = (await (0, server_client_1.callServer)(server, token, 'GET', `/v1/tasks?scope=${options.mine ? 'mine' : 'visible'}`)).body;
527
+ const query = new URLSearchParams({ scope: options.mine ? 'mine' : 'visible' });
528
+ if (options.since) {
529
+ query.set('since', options.since);
530
+ }
531
+ if (options.until) {
532
+ query.set('until', options.until);
533
+ }
534
+ const { tasks } = (await (0, server_client_1.callServer)(server, token, 'GET', `/v1/tasks?${query}`)).body;
526
535
  if (options.json) {
527
536
  environment.stdout.write(`${JSON.stringify(tasks, null, 2)}\n`);
528
537
  return;
529
538
  }
530
539
  if (tasks.length === 0) {
531
- environment.stdout.write(`${serverName}: no task ran in the runs it keeps\n`);
540
+ environment.stdout.write(`${serverName}: no task ran in the runs it keeps${options.since || options.until ? ' of that period' : ''}\n`);
532
541
  return;
533
542
  }
534
543
  for (const line of (0, remote_status_1.describeTaskStats)(tasks)) {
@@ -5,12 +5,14 @@ const crypto_1 = require("crypto");
5
5
  const promises_1 = require("fs/promises");
6
6
  const path_1 = require("path");
7
7
  const http_1 = require("./http");
8
+ const oauth_1 = require("./oauth");
8
9
  const policy_1 = require("./policy");
9
10
  const overview_1 = require("./overview");
10
11
  // Approving and publishing take a sign-in of the last minutes: the groups of a session are
11
12
  // those of its sign-in, and somebody taken out of a group must not decide for hours.
12
13
  exports.DECIDE_WITHIN_MS = 15 * 60000;
13
14
  const RUN = 'run_[A-Za-z0-9_-]{16}';
15
+ const SANDBOX = 'sbx_[A-Za-z0-9_-]{16}';
14
16
  // a hash of a build file version, checked by the API
15
17
  const HASH = '[A-Za-z0-9:_-]{1,200}';
16
18
  const path = (pattern) => new RegExp(`^/v1/${pattern}$`);
@@ -28,9 +30,19 @@ const READS = [
28
30
  'proposals',
29
31
  `proposals/${HASH}`,
30
32
  `proposals/${HASH}/files`,
33
+ // the build files, and the page of the version a task or run ran
34
+ 'definitions',
35
+ `definitions/${HASH}`,
36
+ `definitions/${HASH}/files`,
37
+ // the person's sandboxes and what changed in them; not their files, nothing that writes
38
+ 'sandboxes',
39
+ `sandboxes/${SANDBOX}`,
40
+ `sandboxes/${SANDBOX}/changes`,
31
41
  ].map(path);
32
42
  // what the buttons do
33
- const ACTIONS = [`runs/${RUN}/(cancel|extend)`].map(path);
43
+ const ACTIONS = [`runs/${RUN}/(cancel|extend|wake)`].map(path);
44
+ // what the delete buttons remove: a sandbox of the person's
45
+ const REMOVALS = [`sandboxes/${SANDBOX}`].map(path);
34
46
  // what needs a recent sign-in
35
47
  const DECISIONS = [`approvals/${RUN}/(approve|reject)`, `proposals/${HASH}/(approve|reject)`].map(path);
36
48
  exports.HEADERS = {
@@ -78,9 +90,11 @@ async function api(context, principal, viewer, now, req, res, url) {
78
90
  if (method !== 'GET') {
79
91
  return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow: 'GET' });
80
92
  }
93
+ const name = (0, oauth_1.displayName)(principal);
81
94
  const session = {
82
95
  iss: principal.iss,
83
96
  sub: principal.sub,
97
+ ...(name ? { name } : {}),
84
98
  observe: grant.observe,
85
99
  seesOthers: grant.observe || (0, overview_1.seesActivity)(viewer),
86
100
  publishes: (0, policy_1.mayPublish)(grant),
@@ -103,15 +117,19 @@ async function api(context, principal, viewer, now, req, res, url) {
103
117
  const read = READS.some((pattern) => pattern.test(target));
104
118
  const action = ACTIONS.some((pattern) => pattern.test(target));
105
119
  const decision = DECISIONS.some((pattern) => pattern.test(target));
106
- if (!read && !action && !decision) {
120
+ const removal = REMOVALS.some((pattern) => pattern.test(target));
121
+ if (!read && !action && !decision && !removal) {
107
122
  return (0, http_1.send)(res, 404, { error: 'not found' });
108
123
  }
124
+ const allow = [read ? 'GET' : '', action || decision ? 'POST' : '', removal ? 'DELETE' : '']
125
+ .filter(Boolean)
126
+ .join(', ');
109
127
  if (method === 'GET' || method === 'HEAD') {
110
128
  if (!read) {
111
- return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
129
+ return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow });
112
130
  }
113
131
  }
114
- else if (method === 'POST' && (action || decision)) {
132
+ else if ((method === 'POST' && (action || decision)) || (method === 'DELETE' && removal)) {
115
133
  // a page of another site can not set the header, nor read the token
116
134
  const given = Buffer.from(String(req.headers['x-hammerkit-csrf'] ?? ''));
117
135
  const expected = Buffer.from(csrf);
@@ -128,7 +146,7 @@ async function api(context, principal, viewer, now, req, res, url) {
128
146
  context.log(`dashboard ${method} ${target} iss=${principal.iss} sub=${principal.sub}`);
129
147
  }
130
148
  else {
131
- return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow: read ? 'GET' : 'POST' });
149
+ return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow });
132
150
  }
133
151
  const forwarded = new URL(`${target}${url.search}`, 'http://localhost');
134
152
  return context.api(principal, req, res, forwarded);
@@ -165,8 +183,8 @@ async function asset(context, req, res, pathname) {
165
183
  const root = context.assets ?? (0, path_1.join)(__dirname, '..', 'dashboard');
166
184
  const file = /^\/assets\/[A-Za-z0-9_-][A-Za-z0-9._-]{0,200}$/.test(pathname)
167
185
  ? pathname.slice(1)
168
- : pathname === '/favicon.svg'
169
- ? 'favicon.svg'
186
+ : pathname === '/logo.png'
187
+ ? 'logo.png'
170
188
  : pathname.startsWith('/assets/') || pathname.startsWith('/.')
171
189
  ? undefined
172
190
  : 'index.html';
@@ -285,7 +285,15 @@ class EndpointGateway {
285
285
  return null;
286
286
  }
287
287
  const session = this.readSession(label, req.headers.cookie);
288
- return session ? { iss: session.iss, sub: session.sub, groups: session.groups, claims: session.claims } : null;
288
+ return session
289
+ ? {
290
+ iss: session.iss,
291
+ sub: session.sub,
292
+ groups: session.groups,
293
+ claims: session.claims,
294
+ ...(session.name ? { name: session.name } : {}),
295
+ }
296
+ : null;
289
297
  }
290
298
  // Whether the browser sends the request for another page than the endpoint's own: an
291
299
  // upgrade from another origin, or a request a page of another site makes (a frame too),
@@ -652,6 +660,7 @@ ${run.state === 'up'
652
660
  host: label,
653
661
  iss: issued.principal.iss,
654
662
  sub: issued.principal.sub,
663
+ ...((0, oauth_1.displayName)(issued.principal) ? { name: (0, oauth_1.displayName)(issued.principal) } : {}),
655
664
  groups: issued.principal.groups,
656
665
  claims: this.matchedClaims(issued.principal),
657
666
  exp,
@@ -1,7 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TokenVerifier = exports.quote = exports.TokenError = void 0;
3
+ exports.TokenVerifier = exports.quote = exports.TokenError = exports.displayName = void 0;
4
4
  const crypto_1 = require("crypto");
5
+ /**
6
+ * How pages name a person: the `name` of their token, else `preferred_username`, else `email`
7
+ * (with the scopes `profile email`); none with `openid` alone, which carries the subject only.
8
+ */
9
+ function displayName(principal) {
10
+ const claims = principal.claims ?? {};
11
+ for (const value of [principal.name, claims.name, claims.preferred_username, claims.email]) {
12
+ const trimmed = value?.trim();
13
+ if (trimmed) {
14
+ return trimmed.slice(0, 100);
15
+ }
16
+ }
17
+ return undefined;
18
+ }
19
+ exports.displayName = displayName;
5
20
  // The reason is for the operator's log, never for the caller.
6
21
  class TokenError extends Error {
7
22
  }
@@ -5,6 +5,7 @@ const policy_1 = require("./policy");
5
5
  const runs_1 = require("./runs");
6
6
  const service_runs_1 = require("./service-runs");
7
7
  const repo_1 = require("./repo");
8
+ const units_1 = require("../utils/units");
8
9
  var wait_reason_1 = require("./wait-reason");
9
10
  Object.defineProperty(exports, "describeWait", { enumerable: true, get: function () { return wait_reason_1.describeWait; } });
10
11
  function getViewer(config, principal) {
@@ -21,9 +22,14 @@ function runActions(viewer, run) {
21
22
  }
22
23
  const mine = owns(viewer, run);
23
24
  const may = {
24
- ...(mine || decides(viewer, run) ? { stop: true } : {}),
25
+ ...(mine || decides(viewer, run) || (run.up && (0, service_runs_1.mayPublishEndpoints)(viewer.grant, run))
26
+ ? { stop: true }
27
+ : {}),
25
28
  ...(run.up && run.upAt && (mine || (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run)) ? { extend: true } : {}),
26
29
  ...(mayDecide(viewer, run) ? { decide: true } : {}),
30
+ ...(run.up && run.state === 'asleep' && (mine || (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run))
31
+ ? { wake: true }
32
+ : {}),
27
33
  };
28
34
  return Object.keys(may).length > 0 ? may : undefined;
29
35
  }
@@ -102,7 +108,9 @@ function toListedRun(viewer, run, queue) {
102
108
  ]));
103
109
  return {
104
110
  ...rest,
105
- ...(mine ? { plan, options } : { owner: { iss: owner.iss, sub: owner.sub } }),
111
+ ...(mine
112
+ ? { plan, options }
113
+ : { owner: { iss: owner.iss, sub: owner.sub, ...(owner.name ? { name: owner.name } : {}) } }),
106
114
  ...(up
107
115
  ? {
108
116
  up: {
@@ -209,7 +217,7 @@ const STATES = [
209
217
  'rejected',
210
218
  ];
211
219
  // `scope`, `state` (a comma separated list) and `kind` of GET /v1/runs.
212
- function parseRunFilter(params, grant) {
220
+ function parseRunFilter(params, grant, now = Date.now()) {
213
221
  const scope = params.get('scope') ?? 'mine';
214
222
  if (scope !== 'mine' && scope !== 'visible' && scope !== 'all') {
215
223
  throw new Error('scope: mine, visible or all');
@@ -234,9 +242,35 @@ function parseRunFilter(params, grant) {
234
242
  if (id !== undefined && !/^run_[A-Za-z0-9_-]{16}$/.test(id)) {
235
243
  throw new Error('id: the id of a run, run_...');
236
244
  }
237
- return { scope, ...(states ? { states } : {}), ...(kind ? { kind } : {}), ...(id ? { id } : {}) };
245
+ const since = parseMoment(params.get('since'), now, 'since');
246
+ const until = parseMoment(params.get('until'), now, 'until');
247
+ if (since !== undefined && until !== undefined && until < since) {
248
+ throw new Error('until: after since');
249
+ }
250
+ return {
251
+ scope,
252
+ ...(states ? { states } : {}),
253
+ ...(kind ? { kind } : {}),
254
+ ...(id ? { id } : {}),
255
+ ...(since !== undefined ? { since } : {}),
256
+ ...(until !== undefined ? { until } : {}),
257
+ };
238
258
  }
239
259
  exports.parseRunFilter = parseRunFilter;
260
+ // `7d` back from now, or a time such as `2026-11-01T08:00:00Z`, in ms
261
+ function parseMoment(value, now, name) {
262
+ if (value === null) {
263
+ return undefined;
264
+ }
265
+ if (/^(\d+(\.\d+)?(ms|s|m|h|d))+$/.test(value)) {
266
+ return now - (0, units_1.parseDuration)(value);
267
+ }
268
+ const at = /^\d{4}-\d{2}-\d{2}/.test(value) ? Date.parse(value) : NaN;
269
+ if (!Number.isFinite(at)) {
270
+ throw new Error(`${name}: a duration back from now, such as 1h, 24h or 7d, or a time, such as 2026-11-01T08:00:00Z`);
271
+ }
272
+ return at;
273
+ }
240
274
  class FilterRefused extends Error {
241
275
  }
242
276
  exports.FilterRefused = FilterRefused;
@@ -244,7 +278,9 @@ function filterRuns(runs, viewer, filter) {
244
278
  return runs.filter((run) => (filter.scope === 'mine' ? owns(viewer, run) : maySee(viewer, run)) &&
245
279
  (!filter.id || run.id === filter.id) &&
246
280
  (!filter.states || filter.states.includes(run.state)) &&
247
- (!filter.kind || (filter.kind === 'services') === !!run.up));
281
+ (!filter.kind || (filter.kind === 'services') === !!run.up) &&
282
+ (filter.since === undefined || Date.parse(run.createdAt) >= filter.since) &&
283
+ (filter.until === undefined || Date.parse(run.createdAt) <= filter.until));
248
284
  }
249
285
  exports.filterRuns = filterRuns;
250
286
  // The machines and what the runs take, with whose the namespaces are where the caller may
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.unavailableExecutor = exports.RunRegistry = exports.MAX_TASK_ERROR = exports.MAX_CAUSES = exports.SANDBOX_ID_PATTERN = exports.RUN_ID_PATTERN = exports.QuotaError = exports.RunError = exports.isFinal = void 0;
4
4
  const crypto_1 = require("crypto");
5
+ const oauth_1 = require("./oauth");
5
6
  const run_logs_1 = require("./run-logs");
6
7
  const units_1 = require("../utils/units");
7
8
  const FINAL_STATES = ['succeeded', 'failed', 'cancelled', 'rejected'];
@@ -192,12 +193,14 @@ class RunRegistry {
192
193
  this.checkUpLimits(owner, spec.up.name);
193
194
  this.checkHosts(owner, spec.up);
194
195
  }
196
+ const name = (0, oauth_1.displayName)(owner);
195
197
  const run = {
196
198
  ...spec,
197
199
  id: `run_${(0, crypto_1.randomBytes)(12).toString('base64url')}`,
198
200
  owner: {
199
201
  iss: owner.iss,
200
202
  sub: owner.sub,
203
+ ...(name ? { name } : {}),
201
204
  ...(spec.up?.sleepTtl ? { groups: [...(owner.groups ?? [])], claims: { ...(owner.claims ?? {}) } } : {}),
202
205
  },
203
206
  state: waiting ? 'awaiting-approval' : 'queued',
@@ -306,8 +309,10 @@ class RunRegistry {
306
309
  }
307
310
  // A queued run is cancelled at once; a running one when its executor stops. A service run
308
311
  // that is up stops its services, and says who stopped it.
309
- cancel(owner, id, canUse) {
310
- const entry = this.find(owner, id, canUse);
312
+ // Stops a run for its owner, who decides about it, and, a service run, whoever `mayStop` says.
313
+ cancel(owner, id, canUse, mayStop) {
314
+ const other = this.entries.get(id);
315
+ const entry = this.find(owner, id, canUse) ?? (other?.run.up && mayStop?.(this.copy(other.run)) ? other : undefined);
311
316
  if (!entry) {
312
317
  return undefined;
313
318
  }
@@ -336,6 +341,24 @@ class RunRegistry {
336
341
  this.expireLater(entry);
337
342
  return { status: 'ok', run: this.copy(entry.run) };
338
343
  }
344
+ /**
345
+ * Wakes a service run that sleeps, for its owner or who `mayOpen` it, as a request to its
346
+ * endpoints would (visit): with the same check of its owner (`mayWake`) and the same places.
347
+ * Answers why it did not wake when it could not.
348
+ */
349
+ wake(caller, id, mayOpen, mayWake) {
350
+ const entry = this.entries.get(id);
351
+ if (!entry?.run.up || !(this.owns(caller, entry) || mayOpen?.(this.copy(entry.run)))) {
352
+ return { status: 'not-found' };
353
+ }
354
+ if (entry.run.state !== 'asleep') {
355
+ return { status: 'not-asleep', state: entry.run.state };
356
+ }
357
+ const refused = this.visit(id, mayWake);
358
+ return refused
359
+ ? { status: 'refused', reason: refused, run: this.copy(entry.run) }
360
+ : { status: 'ok', run: this.copy(entry.run) };
361
+ }
339
362
  /**
340
363
  * The service run an endpoint host belongs to: the newest that is up, so one that replaces
341
364
  * a run of the same name takes its hosts over as soon as it is up; else the newest that
@@ -109,7 +109,7 @@ function streamEvents(registry, config, principal, id, req, res, url) {
109
109
  });
110
110
  }
111
111
  async function handleRuns(config, registry, definitions, sources, sandboxes, planning, repos, endpointUrl, log, principal, req, res, url) {
112
- const match = /^\/v1\/runs(?:\/([^/]+)(?:\/(cancel|events|logs|extend))?)?$/.exec(url.pathname);
112
+ const match = /^\/v1\/runs(?:\/([^/]+)(?:\/(cancel|events|logs|extend|wake))?)?$/.exec(url.pathname);
113
113
  if (!match) {
114
114
  return false;
115
115
  }
@@ -148,7 +148,8 @@ async function handleRuns(config, registry, definitions, sources, sandboxes, pla
148
148
  (0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
149
149
  return true;
150
150
  }
151
- const run = registry.cancel(principal, id, accountsOf(config, principal));
151
+ const grant = (0, policy_1.getGrant)(config.entitlements, principal);
152
+ const run = registry.cancel(principal, id, accountsOf(config, principal), (up) => (0, service_runs_1.mayPublishEndpoints)(grant, up));
152
153
  log(`cancel iss=${principal.iss} sub=${principal.sub} run=${id} found=${Boolean(run)}`);
153
154
  run ? (0, http_2.send)(res, 200, toRunResponse(run)) : (0, http_2.send)(res, 404, { error: 'not found' });
154
155
  }
@@ -159,6 +160,28 @@ async function handleRuns(config, registry, definitions, sources, sandboxes, pla
159
160
  }
160
161
  await extendRun(config, registry, log, principal, id, req, res);
161
162
  }
163
+ else if (action === 'wake') {
164
+ if (method !== 'POST') {
165
+ (0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
166
+ return true;
167
+ }
168
+ // as a request to one of its endpoints would, with the same check of its owner
169
+ const grant = (0, policy_1.getGrant)(config.entitlements, principal);
170
+ const result = registry.wake(principal, id, (run) => (0, service_runs_1.mayOpenEndpoints)(grant, run), (run) => refuseWake(config, repos, run));
171
+ log(`wake iss=${principal.iss} sub=${principal.sub} run=${id} result=${result.status}`);
172
+ if (result.status === 'not-found') {
173
+ (0, http_2.send)(res, 404, { error: 'not found' });
174
+ }
175
+ else if (result.status === 'not-asleep') {
176
+ (0, http_2.send)(res, 409, { error: `the run is ${result.state}, only a service run that sleeps wakes` });
177
+ }
178
+ else if (result.status === 'refused') {
179
+ (0, http_2.send)(res, 409, { error: result.reason });
180
+ }
181
+ else {
182
+ (0, http_2.send)(res, 200, toRunResponse(result.run));
183
+ }
184
+ }
162
185
  else if (method !== 'GET') {
163
186
  (0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'GET' });
164
187
  }
@@ -303,7 +326,7 @@ function toRunResponse(run, queue, named = false) {
303
326
  const { owner, ...visible } = run;
304
327
  return {
305
328
  ...visible,
306
- ...(named ? { owner: { iss: owner.iss, sub: owner.sub } } : {}),
329
+ ...(named ? { owner: { iss: owner.iss, sub: owner.sub, ...(owner.name ? { name: owner.name } : {}) } } : {}),
307
330
  results: run.results ?? {},
308
331
  ...(queue ? { queue } : {}),
309
332
  };
@@ -518,8 +541,11 @@ async function submitRun(config, registry, definitions, sources, sandboxes, plan
518
541
  : undefined;
519
542
  // a run that may sleep keeps what its owner is entitled by, for the check of a wake
520
543
  const claimNames = new Set(config.entitlements.flatMap((entitlement) => Object.keys(entitlement.claims?.match ?? {})));
544
+ const name = (0, oauth_1.displayName)(principal);
521
545
  const owner = {
522
546
  ...principal,
547
+ // how the pages name them, which the claims kept no longer tell
548
+ ...(name ? { name } : {}),
523
549
  claims: Object.fromEntries(Object.entries(principal.claims).filter(([name]) => claimNames.has(name))),
524
550
  };
525
551
  // A service run of the caller of this name that runs the same build file, services and
@@ -702,7 +728,13 @@ async function handleApi(api, principal, req, res, url) {
702
728
  return;
703
729
  }
704
730
  const runs = (0, overview_1.filterRuns)(registry.all(), viewer, filter).map((run) => (0, overview_1.toListedRun)(viewer, run));
705
- (0, http_2.send)(res, 200, { tasks: (0, timings_1.getTaskStats)(runs, Date.now()) });
731
+ const oldest = (0, timings_1.getOldestCounted)(runs);
732
+ // where in the repository the build file of the last run is, as its version says
733
+ const tasks = (0, timings_1.getTaskStats)(runs, Date.now()).map((stats) => {
734
+ const entry = stats.last.definition ? api.definitions.find(stats.last.definition)?.entry : undefined;
735
+ return entry ? { ...stats, last: { ...stats.last, entry } } : stats;
736
+ });
737
+ (0, http_2.send)(res, 200, { tasks, ...(oldest ? { oldest } : {}) });
706
738
  return;
707
739
  }
708
740
  if (await (0, approvals_api_1.handleApprovals)(config, registry, log, principal, req, res, url)) {