crewdesk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +54 -0
- package/apps/server/dist/cli.js +2050 -0
- package/apps/web/dist/assets/index-hoPwNvfD.js +18 -0
- package/apps/web/dist/assets/index-lYVPS1ZE.css +1 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/index.html +14 -0
- package/bin/crewdesk-hook.mjs +8 -0
- package/bin/crewdesk.mjs +23 -0
- package/docs/API.md +36 -0
- package/docs/CONFIGURATION.md +40 -0
- package/docs/DEVELOPMENT.md +25 -0
- package/docs/HOOKS.md +28 -0
- package/docs/INSTALLATION.md +37 -0
- package/docs/SECURITY.md +10 -0
- package/docs/TROUBLESHOOTING.md +11 -0
- package/package.json +75 -0
- package/packages/hook/dist/hook.js +457 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking: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}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-4xl:56rem;--container-5xl:64rem;--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: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-md:.375rem;--radius-xl:.75rem;--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);--color-card:var(--card);--color-card-2:var(--card-2);--color-line:var(--line);--color-fg:var(--fg);--color-muted:var(--muted);--color-st-blue:#3b82f6;--color-st-green:#22c55e;--color-st-yellow:#eab308;--color-st-orange:#f97316;--color-st-red:#ef4444;--color-st-purple:#a855f7;--color-st-gray:#6b7280}}@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;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}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-72{max-height:calc(var(--spacing) * 72)}.min-h-0{min-height:0}.w-2{width:calc(var(--spacing) * 2)}.w-16{width:calc(var(--spacing) * 16)}.w-36{width:calc(var(--spacing) * 36)}.w-44{width:calc(var(--spacing) * 44)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.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)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.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-line>:not(:last-child)){border-color:var(--color-line)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-line{border-color:var(--color-line)}.border-st-blue{border-color:var(--color-st-blue)}.border-st-orange{border-color:var(--color-st-orange)}.border-st-orange\/60{border-color:#f9731699}@supports (color:color-mix(in lab,red,red)){.border-st-orange\/60{border-color:color-mix(in oklab,var(--color-st-orange) 60%,transparent)}}.border-st-purple{border-color:var(--color-st-purple)}.border-st-red{border-color:var(--color-st-red)}.border-st-yellow{border-color:var(--color-st-yellow)}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-card{background-color:var(--color-card)}.bg-card-2{background-color:var(--color-card-2)}.bg-st-blue{background-color:var(--color-st-blue)}.bg-st-blue\/15{background-color:#3b82f626}@supports (color:color-mix(in lab,red,red)){.bg-st-blue\/15{background-color:color-mix(in oklab,var(--color-st-blue) 15%,transparent)}}.bg-st-gray{background-color:var(--color-st-gray)}.bg-st-green{background-color:var(--color-st-green)}.bg-st-orange{background-color:var(--color-st-orange)}.bg-st-orange\/15{background-color:#f9731626}@supports (color:color-mix(in lab,red,red)){.bg-st-orange\/15{background-color:color-mix(in oklab,var(--color-st-orange) 15%,transparent)}}.bg-st-orange\/20{background-color:#f9731633}@supports (color:color-mix(in lab,red,red)){.bg-st-orange\/20{background-color:color-mix(in oklab,var(--color-st-orange) 20%,transparent)}}.bg-st-purple{background-color:var(--color-st-purple)}.bg-st-purple\/15{background-color:#a855f726}@supports (color:color-mix(in lab,red,red)){.bg-st-purple\/15{background-color:color-mix(in oklab,var(--color-st-purple) 15%,transparent)}}.bg-st-red{background-color:var(--color-st-red)}.bg-st-red\/10{background-color:#ef44441a}@supports (color:color-mix(in lab,red,red)){.bg-st-red\/10{background-color:color-mix(in oklab,var(--color-st-red) 10%,transparent)}}.bg-st-red\/15{background-color:#ef444426}@supports (color:color-mix(in lab,red,red)){.bg-st-red\/15{background-color:color-mix(in oklab,var(--color-st-red) 15%,transparent)}}.bg-st-yellow{background-color:var(--color-st-yellow)}.bg-st-yellow\/15{background-color:#eab30826}@supports (color:color-mix(in lab,red,red)){.bg-st-yellow\/15{background-color:color-mix(in oklab,var(--color-st-yellow) 15%,transparent)}}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.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-2\.5{padding-block:calc(var(--spacing) * 2.5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-1{padding-right:var(--spacing)}.text-center{text-align:center}.text-left{text-align:left}.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-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.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)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-black{color:var(--color-black)}.text-fg{color:var(--color-fg)}.text-muted{color:var(--color-muted)}.text-st-blue{color:var(--color-st-blue)}.text-st-gray{color:var(--color-st-gray)}.text-st-green{color:var(--color-st-green)}.text-st-orange{color:var(--color-st-orange)}.text-st-purple{color:var(--color-st-purple)}.text-st-red{color:var(--color-st-red)}.text-st-yellow{color:var(--color-st-yellow)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px 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)}.ring-2{--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)}.ring-st-blue\/60{--tw-ring-color:#3b82f699}@supports (color:color-mix(in lab,red,red)){.ring-st-blue\/60{--tw-ring-color:color-mix(in oklab, var(--color-st-blue) 60%, transparent)}}.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{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.placeholder\:text-muted::placeholder{color:var(--color-muted)}@media(hover:hover){.hover\:border-st-blue\/60:hover{border-color:#3b82f699}@supports (color:color-mix(in lab,red,red)){.hover\:border-st-blue\/60:hover{border-color:color-mix(in oklab,var(--color-st-blue) 60%,transparent)}}.hover\:border-st-orange:hover{border-color:var(--color-st-orange)}.hover\:bg-card-2:hover{background-color:var(--color-card-2)}.hover\:bg-st-green\/85:hover{background-color:#22c55ed9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-green\/85:hover{background-color:color-mix(in oklab,var(--color-st-green) 85%,transparent)}}.hover\:bg-st-orange\/85:hover{background-color:#f97316d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-orange\/85:hover{background-color:color-mix(in oklab,var(--color-st-orange) 85%,transparent)}}.hover\:bg-st-red\/85:hover{background-color:#ef4444d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-red\/85:hover{background-color:color-mix(in oklab,var(--color-st-red) 85%,transparent)}}.hover\:bg-st-yellow\/85:hover{background-color:#eab308d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-yellow\/85:hover{background-color:color-mix(in oklab,var(--color-st-yellow) 85%,transparent)}}.hover\:text-fg:hover{color:var(--color-fg)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-st-blue:focus{border-color:var(--color-st-blue)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:48rem){.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:64rem){.lg\:inline{display:inline}.lg\:grid-cols-\[320px_1fr\]{grid-template-columns:320px 1fr}}}:root{--bg:#f5f7fa;--card:#fff;--card-2:#eef1f5;--line:#d9dee6;--fg:#111827;--muted:#5b6574;color-scheme:light}:root.dark{--bg:#0b0d10;--card:#151922;--card-2:#1b202b;--line:#252b36;--fg:#f4f7fa;--muted:#8b95a5;color-scheme:dark}html,body,#root{height:100%}body{background:var(--bg);color:var(--fg);font-family:var(--font-sans);-webkit-font-smoothing:antialiased}:focus-visible{outline-offset:2px;outline:2px solid #3b82f6}.pulse-dot{animation:1.6s ease-in-out infinite pulse-dot}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.35}}@property --tw-space-y-reverse{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-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}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#151922"/><circle cx="11" cy="11" r="4" fill="#3B82F6"/><circle cx="21" cy="11" r="4" fill="#A855F7"/><circle cx="11" cy="21" r="4" fill="#F97316"/><circle cx="21" cy="21" r="4" fill="#22C55E"/></svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
7
|
+
<title>Crewdesk</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-hoPwNvfD.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-lYVPS1ZE.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code hook bridge. Must never break Claude Code: any failure exits 0 silently.
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const entry = fileURLToPath(new URL("../packages/hook/dist/hook.js", import.meta.url));
|
|
7
|
+
if (existsSync(entry)) await import(pathToFileURL(entry).href);
|
|
8
|
+
else process.exit(0);
|
package/bin/crewdesk.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const entry = fileURLToPath(new URL("../apps/server/dist/cli.js", import.meta.url));
|
|
6
|
+
if (!existsSync(entry)) {
|
|
7
|
+
console.error("crewdesk is not built yet. Run `pnpm install && pnpm build` in the repository.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
// Node 22 warns that node:sqlite is experimental on every command. Hide just that warning; it has to be
|
|
11
|
+
// installed here, before the bundle (whose node:sqlite import is hoisted) is loaded.
|
|
12
|
+
const emitWarning = process.emitWarning.bind(process);
|
|
13
|
+
process.emitWarning = (warning, ...rest) => {
|
|
14
|
+
const type = typeof rest[0] === "string" ? rest[0] : rest[0]?.type ?? warning?.name;
|
|
15
|
+
if (type === "ExperimentalWarning" && /sqlite/i.test(String(warning?.message ?? warning))) return;
|
|
16
|
+
return emitWarning(warning, ...rest);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const { main } = await import(pathToFileURL(entry).href);
|
|
20
|
+
await main(process.argv.slice(2)).catch((err) => {
|
|
21
|
+
console.error(err?.message ?? err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
package/docs/API.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
Base URL: `http://127.0.0.1:7777`. JSON only. All input is validated with Zod.
|
|
4
|
+
|
|
5
|
+
Errors use the form `{"error":{"code","message","details?"}}`, with status 400, 401, 403, 404, 409, 422 or 500.
|
|
6
|
+
|
|
7
|
+
| Method | Path | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| GET | `/health` | `{status, database, websocket, uptime, version, pid}` |
|
|
10
|
+
| POST | `/api/agents/register` | `{sessionId, name?, project?, cwd?, pid?, account?}` returns `{success, agentId, agent}`. Idempotent per session |
|
|
11
|
+
| POST | `/api/agents/heartbeat` | `{sessionId, status?, activity?}` |
|
|
12
|
+
| GET | `/api/agents`, `/api/agents/:id` | The detail call includes recent events |
|
|
13
|
+
| PATCH | `/api/agents/:id` | `{name?, status?, activity?}` |
|
|
14
|
+
| DELETE | `/api/agents/:id` | Removes the agent and its history |
|
|
15
|
+
| POST | `/api/events` | `{sessionId, type, message?, priority?, payload?, agent?}` returns `{id, status, event}` |
|
|
16
|
+
| GET | `/api/events` | `?status=pending&type=question&agent=&session=&project=&q=&limit=&before=` |
|
|
17
|
+
| GET | `/api/events/pending` | Everything in PENDING or PROCESSING |
|
|
18
|
+
| GET | `/api/events/:id` | |
|
|
19
|
+
| POST | `/api/events/:id/respond` | `{action: allow, deny, answer, prompt or dismiss, sessionId?, response? (alias answer), answers?, confirm?}` |
|
|
20
|
+
| POST | `/api/events/:id/cancel` | `{sessionId?, reason?}` |
|
|
21
|
+
| GET | `/api/events/:id/wait` | `?sessionId=&timeout=ms`. Long-poll for hooks; returns `{state: pending, resolved or cancelled}` |
|
|
22
|
+
| GET | `/api/policies`, `/api/settings` | |
|
|
23
|
+
| WS | `/ws` | Server-push only: `hello`, `agent.created/updated/removed`, `event.created/updated/resolved/deleted` |
|
|
24
|
+
|
|
25
|
+
## Event lifecycle
|
|
26
|
+
|
|
27
|
+
`PENDING` → decision recorded → `PROCESSING` → hook received it → `RESOLVED`.
|
|
28
|
+
|
|
29
|
+
A request can also end as `CANCELLED` (withdrawn, handled in the terminal, session ended, expired) or `FAILED` (the session stopped waiting before delivery).
|
|
30
|
+
|
|
31
|
+
## Routing rules
|
|
32
|
+
|
|
33
|
+
- A response is routed only by event id → session id → transport.
|
|
34
|
+
- If `respond`, `cancel` or `wait` gets a `sessionId` that doesn't match the event, it returns `403 SESSION_MISMATCH`.
|
|
35
|
+
- Answering twice returns `409 EVENT_NOT_PENDING`.
|
|
36
|
+
- Allowing a destructive permission without `confirm: true` returns `422 CONFIRMATION_REQUIRED`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
The config file is `~/.crewdesk/config.json`. Environment variables override it.
|
|
4
|
+
|
|
5
|
+
| Key | Env | Default | Meaning |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `port` | `CREWDESK_PORT` | 7777 | HTTP/WebSocket port |
|
|
8
|
+
| `host` | `CREWDESK_HOST` | 127.0.0.1 | Bind address (keep loopback) |
|
|
9
|
+
| `database` | `CREWDESK_DB` | `~/.crewdesk/data.db` | SQLite file |
|
|
10
|
+
| `heartbeatTimeout` | `CREWDESK_HEARTBEAT_TIMEOUT` | 30000 | ms of silence before a session with no known pid is OFFLINE (10x while WORKING) |
|
|
11
|
+
| `logLevel` | `LOG_LEVEL` | info | error, warn, info or debug |
|
|
12
|
+
| `notifications`, `sound` | `CREWDESK_NOTIFICATIONS`, `CREWDESK_SOUND` | true | UI defaults |
|
|
13
|
+
| `waiterGraceMs` | `CREWDESK_WAITER_GRACE` | 5000 | How long a request survives its hook disconnecting before it is marked "handled in terminal" |
|
|
14
|
+
| `idlePrompts` | `CREWDESK_IDLE_PROMPTS` | true | Stop hook waits for a follow-up from the dashboard |
|
|
15
|
+
| `idleWaitSeconds` | `CREWDESK_IDLE_WAIT` | 1800 | Max idle wait, then control returns to the terminal |
|
|
16
|
+
| `questionWaitSeconds` | `CREWDESK_QUESTION_WAIT` | 600 | Max wait for a dashboard answer before the terminal shows the question |
|
|
17
|
+
| `permissionWaitSeconds` | `CREWDESK_PERMISSION_WAIT` | 43200 | Max permission wait (the terminal dialog stays usable meanwhile) |
|
|
18
|
+
| `permissionExpiryMs` | `CREWDESK_PERMISSION_EXPIRY` | 0 | Auto-cancel pending permissions older than this. 0 turns it off |
|
|
19
|
+
|
|
20
|
+
Per-session environment, set before launching `claude`:
|
|
21
|
+
|
|
22
|
+
- `CREWDESK_AGENT_NAME`: display name for the session.
|
|
23
|
+
- `CREWDESK_PROJECT`: overrides the project name (default: the folder name).
|
|
24
|
+
- `CREWDESK_URL`: sends the session to a different hub.
|
|
25
|
+
|
|
26
|
+
## Permission policies
|
|
27
|
+
|
|
28
|
+
`~/.crewdesk/policies.yaml` is reloaded automatically. The first matching rule wins.
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
permissions:
|
|
32
|
+
- tool: "Bash" # optional tool-name glob
|
|
33
|
+
match: "git status" # glob over the command (Bash) or file path (Edit/Write)
|
|
34
|
+
action: allow # allow | deny | ask
|
|
35
|
+
- match: "curl *"
|
|
36
|
+
action: deny
|
|
37
|
+
reason: "No network from agents"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Commands that are destructive (`rm`, `git reset --hard`, `DROP TABLE`, ...) or chained (`&&`, `;`, `|`, `$(...)`, redirects) are never auto-allowed. They always reach you.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
```text
|
|
4
|
+
apps/server Fastify hub: api/, services/, transport/ (SessionTransport + HookTransport), db/, websocket/, cli/
|
|
5
|
+
apps/web React 19 + Vite + Tailwind v4 dashboard
|
|
6
|
+
packages/shared Types, Zod schemas, operation/risk helpers
|
|
7
|
+
packages/hook crewdesk-hook bridge (bundled, no zod, fast startup)
|
|
8
|
+
bin/ crewdesk and crewdesk-hook launchers
|
|
9
|
+
test/e2e Playwright specs (real hub + real hook processes)
|
|
10
|
+
test/live Opt-in tests against a real `claude -p`
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Command | Does |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `pnpm dev` | Hub (tsx watch) + Vite on :5173, proxied to the hub |
|
|
16
|
+
| `pnpm build` | Builds all packages |
|
|
17
|
+
| `pnpm test` | Vitest: API, routing, persistence, policies, installer, hook bridge |
|
|
18
|
+
| `pnpm test:e2e` | Playwright. Needs `pnpm build` first |
|
|
19
|
+
| `pnpm test:live` | Real Claude Code sessions. Uses tokens; hooks are passed via `--settings` |
|
|
20
|
+
| `pnpm typecheck` | Type-checks every package |
|
|
21
|
+
| `bash scripts/smoke.sh` | macOS/Linux: installer, daemon, pid detection, fail-open against a throwaway HOME. Needs `pnpm build` |
|
|
22
|
+
|
|
23
|
+
CI (`.github/workflows/ci.yml`) runs all of the above on Linux, macOS and Windows with Node 22.13 and 24, plus an `npm pack` + global-install check. Pushing a `v*` tag that matches `package.json` publishes to npm (`release.yml`, needs the `NPM_TOKEN` secret).
|
|
24
|
+
|
|
25
|
+
To add a new delivery mechanism, implement `SessionTransport` (`apps/server/src/transport/transport.ts`) and register it in `RoutingService`.
|
package/docs/HOOKS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
`crewdesk configure` registers these hooks. Each one runs `node <repo>/bin/crewdesk-hook.mjs <sub>`.
|
|
4
|
+
|
|
5
|
+
| Event | Sub-command | Timeout (s) | Behaviour |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| SessionStart | `register` | 30, async | Registers the session (id, cwd, project, `CLAUDE_CONFIG_DIR` account, `CLAUDE_PID`) |
|
|
8
|
+
| UserPromptSubmit | `prompt` | 10, async | Marks WORKING and cancels any pending idle prompt |
|
|
9
|
+
| Notification | `notification` | 10, async | `permission_prompt` and `idle_prompt` are logged silently; others become dismissable notices |
|
|
10
|
+
| PermissionRequest | `permission` | 86400 | Creates the request and long-polls `GET /api/events/:id/wait`; prints `decision.behavior` allow or deny |
|
|
11
|
+
| PreToolUse (`AskUserQuestion`) | `question` | 86400 | Prints `permissionDecision: allow` plus `updatedInput.answers` |
|
|
12
|
+
| Stop | `stop` | 86400 | Logs completion; optionally waits and prints `{"decision":"block","reason":"<your prompt>"}` |
|
|
13
|
+
| SessionEnd | `session-end` | 5 | Marks OFFLINE and cancels outstanding requests |
|
|
14
|
+
|
|
15
|
+
## Observed Claude Code behaviour (2.1.284, Windows)
|
|
16
|
+
|
|
17
|
+
- **PermissionRequest:** the terminal dialog is shown while the hook runs. If the user answers in the terminal, Claude Code kills the hook, and the hub notices the long-poll dropped and cancels the card after `waiterGraceMs` ("first answer wins").
|
|
18
|
+
- **PreToolUse on AskUserQuestion:** the question UI is not shown while the hook runs. That is why the question wait has a time limit and an "Answer in terminal" button.
|
|
19
|
+
- **Stop with `decision: block`:** the `reason` becomes Claude's next instruction. `stop_hook_active` is true on the follow-up stop.
|
|
20
|
+
- **Environment:** `CLAUDE_PID`, `CLAUDE_CODE_SESSION_ID`, `CLAUDE_PROJECT_DIR` and `CLAUDE_CONFIG_DIR` are exported to hooks. On Windows, hooks run under Git Bash.
|
|
21
|
+
- **Not verified:** whether the terminal accepts typing while the Stop hook waits. If it doesn't, press **Back to terminal** on the dashboard or lower `idleWaitSeconds`. Set `idlePrompts: false` to turn the feature off.
|
|
22
|
+
|
|
23
|
+
## Failure behaviour
|
|
24
|
+
|
|
25
|
+
Hooks always exit 0.
|
|
26
|
+
- **Hub unreachable:** retried at 100 ms, 500 ms, 1 s and 2 s (5 attempts). After that the hook prints nothing, so Claude Code proceeds normally.
|
|
27
|
+
- **Hub restart:** a waiting hook keeps retrying its long-poll for about 60 s, so the hub can restart without losing the request.
|
|
28
|
+
- **Debugging:** set `CREWDESK_HOOK_DEBUG=1` to log to `~/.crewdesk/logs/hook.log`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
Requirements: Node.js 22.13 or newer (the hub uses the built-in `node:sqlite`) and Claude Code 2.1 or newer.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g crewdesk
|
|
7
|
+
crewdesk configure
|
|
8
|
+
crewdesk start
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Install globally. The hooks written by `configure` point at the installed `crewdesk-hook` script, so a temporary `npx` copy would break them.
|
|
12
|
+
|
|
13
|
+
From source (needs pnpm 10):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone <this repo> crewdesk && cd crewdesk
|
|
17
|
+
pnpm install && pnpm build
|
|
18
|
+
npm link
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Running `crewdesk` with no arguments on first run starts a wizard. It creates the database, configures hooks, starts the hub and opens the dashboard.
|
|
22
|
+
|
|
23
|
+
## What `configure` changes
|
|
24
|
+
|
|
25
|
+
1. It detects `~/.claude`, every `~/.claude-accounts/<name>` directory that has a `settings.json` or `.claude.json`, and `$CLAUDE_CONFIG_DIR`.
|
|
26
|
+
2. It asks which ones to use. You can skip the prompt with `--all` or `--dir <path>` (repeatable), and preview the changes with `--dry-run`.
|
|
27
|
+
3. It backs up each `settings.json` to `~/.crewdesk/backups/<account>-settings-<timestamp>.json`.
|
|
28
|
+
4. It adds the hooks, writes the file atomically, re-reads it to check it, and restores the backup on any failure.
|
|
29
|
+
5. It never changes hooks it did not install. Its own hooks are recognised by `crewdesk-hook` in the command.
|
|
30
|
+
|
|
31
|
+
## Uninstall
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
crewdesk uninstall # removes only our hooks, stops the hub, asks about the database
|
|
35
|
+
crewdesk uninstall --keep-data # or --delete-data
|
|
36
|
+
npm rm -g crewdesk
|
|
37
|
+
```
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
- **Local only:** the hub binds to `127.0.0.1` by default.
|
|
4
|
+
- **Browser protection:** requests with a non-loopback `Host` header (DNS rebinding) or a cross-site `Origin` are rejected with 403, so a web page open in your browser cannot approve permissions.
|
|
5
|
+
- **No remote execution:** the browser never executes anything through the hub. The WebSocket is push-only, and the API only records decisions for requests that Claude Code itself created.
|
|
6
|
+
- **Validation:** every payload is validated (Zod), including session and event id formats. Malformed JSON gets 400. Bodies are capped at 1 MB.
|
|
7
|
+
- **Explicit auto-decisions only:** nothing is decided automatically unless you write a policy rule. Destructive or chained shell commands always need a human, and allowing a destructive command needs a second confirmation.
|
|
8
|
+
- **Graceful shutdown:** `crewdesk stop` uses a random per-run token stored in `hub.token`.
|
|
9
|
+
- **Hooks fail open:** they never block Claude Code when the hub is unavailable.
|
|
10
|
+
- **Not yet supported:** authentication and remote access. Do not bind to `0.0.0.0` on an untrusted network.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
**Hub not starting:** run `crewdesk doctor`. It checks the port, database, Node version, Claude Code, hooks and build. The log is at `crewdesk logs`.
|
|
4
|
+
|
|
5
|
+
**A Claude session does not appear:** check that `crewdesk doctor` lists hooks for the account you launched with (`CLAUDE_CONFIG_DIR`). Restart the session or run `/hooks`. Then set `CREWDESK_HOOK_DEBUG=1` and read `~/.crewdesk/logs/hook.log`.
|
|
6
|
+
|
|
7
|
+
**A question appears but the answer never arrives:** the event stays PENDING or PROCESSING until the hook picks it up. Check the event in History (session id, status, close reason). If the hook timed out, the event is CANCELLED with the reason "Timed out waiting", and Claude asked in the terminal instead.
|
|
8
|
+
|
|
9
|
+
**A card vanished with "Handled in the terminal":** the hook process ended because someone answered in the terminal, pressed Esc, or the hook timed out. This is the expected "first answer wins" behaviour.
|
|
10
|
+
|
|
11
|
+
**The terminal seems stuck after Claude finishes:** the Stop hook is waiting for a dashboard follow-up. Press **Back to terminal**, lower `idleWaitSeconds`, or set `idlePrompts: false`.
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crewdesk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One local dashboard for all your Claude Code sessions: answer questions, approve permissions and send follow-ups, routed back to the exact session.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"claude",
|
|
8
|
+
"ai-agents",
|
|
9
|
+
"dashboard",
|
|
10
|
+
"hooks",
|
|
11
|
+
"permissions",
|
|
12
|
+
"multi-agent",
|
|
13
|
+
"developer-tools"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Hussein Taha",
|
|
17
|
+
"homepage": "https://github.com/HusseinTaha/crewdesk#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/HusseinTaha/crewdesk.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/HusseinTaha/crewdesk/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"packageManager": "pnpm@10.30.1",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.13"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"crewdesk": "bin/crewdesk.mjs",
|
|
32
|
+
"crewdesk-hook": "bin/crewdesk-hook.mjs"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin/",
|
|
36
|
+
"apps/server/dist/",
|
|
37
|
+
"apps/web/dist/",
|
|
38
|
+
"packages/hook/dist/",
|
|
39
|
+
"docs/*.md",
|
|
40
|
+
"!docs/CLAUDE-CONTROL-CENTER.md",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@fastify/static": "^8.2.0",
|
|
46
|
+
"@fastify/websocket": "^11.2.0",
|
|
47
|
+
"fastify": "^5.6.0",
|
|
48
|
+
"ulid": "^3.0.1",
|
|
49
|
+
"yaml": "^2.8.0",
|
|
50
|
+
"zod": "^4.1.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"dev": "pnpm -r --parallel --filter @crewdesk/server --filter @crewdesk/web dev",
|
|
54
|
+
"dev:server": "pnpm --filter @crewdesk/server dev",
|
|
55
|
+
"dev:web": "pnpm --filter @crewdesk/web dev",
|
|
56
|
+
"build": "pnpm -r build",
|
|
57
|
+
"start": "node bin/crewdesk.mjs start --foreground",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:e2e": "playwright test",
|
|
61
|
+
"test:live": "vitest run --config vitest.live.config.ts",
|
|
62
|
+
"typecheck": "pnpm -r typecheck",
|
|
63
|
+
"lint": "pnpm -r typecheck",
|
|
64
|
+
"prepublishOnly": "pnpm build && pnpm typecheck && pnpm test"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@playwright/test": "^1.55.0",
|
|
68
|
+
"@types/node": "^24.0.0",
|
|
69
|
+
"@types/ws": "^8.18.1",
|
|
70
|
+
"tsx": "^4.23.15",
|
|
71
|
+
"typescript": "^5.9.0",
|
|
72
|
+
"vitest": "^3.2.0",
|
|
73
|
+
"ws": "^8.22.0"
|
|
74
|
+
}
|
|
75
|
+
}
|