scriptorium 0.0.1 → 0.2.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.
Files changed (102) hide show
  1. package/README.md +31 -8
  2. package/bin/scriptorium.js +1 -1
  3. package/build/client/assets/_layout-4p0zIumj.js +1 -0
  4. package/build/client/assets/_layout-CG4jvKXd.js +1 -0
  5. package/build/client/assets/_layout-DUvno75Q.js +1 -0
  6. package/build/client/assets/_layout-tj841ZFj.js +3 -0
  7. package/build/client/assets/auth-shell-CM4lzbUg.js +1 -0
  8. package/build/client/assets/breadcrumbs-CLMk50vh.js +1 -0
  9. package/build/client/assets/{chunk-EPOLDU6W-B-j6nV8T.js → chunk-EPOLDU6W-BT1NqV1s.js} +11 -11
  10. package/build/client/assets/confirm-passkey-sqNHrDN7.js +1 -0
  11. package/build/client/assets/document-title-CaFCBnQ-.js +1 -0
  12. package/build/client/assets/entry.client-B2qwDNf_.js +13 -0
  13. package/build/client/assets/events-BGAz2o24.js +39 -0
  14. package/build/client/assets/files-247Zppti.js +1 -0
  15. package/build/client/assets/files-COflIyot.js +1 -0
  16. package/build/client/assets/files-browser-DKxCza7c.js +1 -0
  17. package/build/client/assets/files.browse-BBMm6lgA.js +1 -0
  18. package/build/client/assets/files.browse-Dq0nnEAB.js +1 -0
  19. package/build/client/assets/index-BumTGMZF.js +1 -0
  20. package/build/client/assets/index-CLO24mr0.js +1 -0
  21. package/build/client/assets/index-D-coH8u1.js +34 -0
  22. package/build/client/assets/index-DxuBmPz-.js +1 -0
  23. package/build/client/assets/jsx-runtime-u17CrQMm.js +1 -0
  24. package/build/client/assets/login-SMI1sC8T.js +1 -0
  25. package/build/client/assets/manifest-27a6038b.js +1 -0
  26. package/build/client/assets/passkeys-Cg-Qx1Gj.js +1 -0
  27. package/build/client/assets/project-events-provider-Cz1h_7Z0.js +1 -0
  28. package/build/client/assets/project-route-Cqnw3iG7.js +1 -0
  29. package/build/client/assets/projects-CcOVpbnV.js +1 -0
  30. package/build/client/assets/projects._projectId.proxy._-l0sNRNKZ.js +1 -0
  31. package/build/client/assets/projects.new-BIAajDmU.js +1 -0
  32. package/build/client/assets/register-CwUWzMvC.js +1 -0
  33. package/build/client/assets/review-browser-CkLIZsiU.js +1 -0
  34. package/build/client/assets/review._mode-CjiM88Yw.js +1 -0
  35. package/build/client/assets/review.uncommitted-DnCOhh-l.js +1 -0
  36. package/build/client/assets/root-DtlaHd7Y.js +1 -0
  37. package/build/client/assets/root-h1ZnuAzH.css +1 -0
  38. package/build/client/assets/{scroll-indicator-CFaTM_rb.js → scroll-indicator-DxagqbkL.js} +8 -8
  39. package/build/client/assets/scrollable-layout-CR2OL0dM.js +1 -0
  40. package/build/client/assets/session-route-Cd_9l4G7.js +1 -0
  41. package/build/client/assets/sessions-provider-xySz_P7U.js +1 -0
  42. package/build/client/assets/settings-CYavUXKt.js +1 -0
  43. package/build/client/assets/sidebar-BpDMNa9u.js +1 -0
  44. package/build/client/assets/use-double-check-BJPDnzvf.js +1 -0
  45. package/build/runtime/app/lib/runtime-config/cache.server.js +15 -0
  46. package/build/runtime/app/lib/runtime-config/cli.server.js +79 -0
  47. package/build/runtime/app/lib/runtime-config/docs.server.js +170 -0
  48. package/build/runtime/app/lib/runtime-config/loader.server.js +69 -0
  49. package/build/runtime/app/lib/runtime-config/schema.server.js +128 -0
  50. package/build/runtime/server/index.js +176 -0
  51. package/build/server/index.js +8989 -4661
  52. package/docs/config.md +12 -8
  53. package/drizzle/20260324172031_curved_junta/migration.sql +20 -0
  54. package/drizzle/20260324172031_curved_junta/snapshot.json +684 -0
  55. package/drizzle/20260327233106_model_usage/migration.sql +15 -0
  56. package/drizzle/20260327233106_model_usage/snapshot.json +830 -0
  57. package/drizzle/20260331000000_projects_and_model_usages/migration.sql +41 -0
  58. package/package.json +16 -3
  59. package/build/client/assets/_layout-D-bxuQBW.js +0 -1
  60. package/build/client/assets/_layout-D2eOWFrG.js +0 -1
  61. package/build/client/assets/_layout-D4m0RbUC.js +0 -1
  62. package/build/client/assets/_layout-DggtTDZp.js +0 -1
  63. package/build/client/assets/auth-shell-FEL5QDuP.js +0 -1
  64. package/build/client/assets/confirm-passkey-ntLsS7_k.js +0 -1
  65. package/build/client/assets/entry.client-BkscxSA6.js +0 -5
  66. package/build/client/assets/file-list-DBDZbrBp.js +0 -1
  67. package/build/client/assets/files-Bzkr7-i7.js +0 -1
  68. package/build/client/assets/files-browser-KNurSsen.js +0 -1
  69. package/build/client/assets/files-cnv1kfgp.js +0 -1
  70. package/build/client/assets/files.browse-9p4xl9MV.js +0 -1
  71. package/build/client/assets/git-CjSY0eSC.js +0 -1
  72. package/build/client/assets/git-T0lLjMNd.js +0 -1
  73. package/build/client/assets/git-browser-DwSOY6QN.js +0 -1
  74. package/build/client/assets/iconify-BDcX0yw8.js +0 -1
  75. package/build/client/assets/index-8zQ7Fizv.js +0 -9
  76. package/build/client/assets/index-BUN6tnpl.js +0 -1
  77. package/build/client/assets/index-BlNoCKAt.js +0 -1
  78. package/build/client/assets/index-g2QB9BbT.js +0 -1
  79. package/build/client/assets/instance-events-provider-DTYnvumj.js +0 -39
  80. package/build/client/assets/instance-route-C25LRYYo.js +0 -1
  81. package/build/client/assets/instances-CzjXhR3c.js +0 -1
  82. package/build/client/assets/instances._instanceId.sessions._sessionId.test-COZIMxji.js +0 -208
  83. package/build/client/assets/instances.new-BZi5xwEV.js +0 -1
  84. package/build/client/assets/login-Bwt79t7_.js +0 -1
  85. package/build/client/assets/magic-string.es-DjU5CGuV.js +0 -10
  86. package/build/client/assets/manifest-3c01c628.js +0 -1
  87. package/build/client/assets/message-card-DJ5C3WNp.js +0 -34
  88. package/build/client/assets/passkeys-DpNQs4bn.js +0 -1
  89. package/build/client/assets/register-DXPj5RLZ.js +0 -1
  90. package/build/client/assets/root-DxD-Skic.css +0 -1
  91. package/build/client/assets/root-DxyfhNRI.js +0 -1
  92. package/build/client/assets/scrollable-layout-CS-vPTNM.js +0 -1
  93. package/build/client/assets/session-route-oxi4s_Qg.js +0 -1
  94. package/build/client/assets/sessions-provider-D1t07kir.js +0 -1
  95. package/build/client/assets/settings-CameSNIM.js +0 -1
  96. package/build/client/assets/sidebar-DRhB33rT.js +0 -1
  97. package/build/client/assets/use-double-check-CbbarqDt.js +0 -1
  98. package/build/runtime/app/lib/runtime-config.server.js +0 -422
  99. package/build/runtime/cli/scriptorium.js +0 -21
  100. package/build/runtime/cli/serve.js +0 -101
  101. /package/build/client/assets/{message-card-BclR_JqA.css → index-BclR_JqA.css} +0 -0
  102. /package/build/client/assets/{instances._instanceId.proxy._-l0sNRNKZ.js → projects._projectId.file-references.resolve-l0sNRNKZ.js} +0 -0
@@ -1 +0,0 @@
1
- import{w as m,u}from"./chunk-EPOLDU6W-B-j6nV8T.js";import{j as e}from"./files.browse-9p4xl9MV.js";import{I as a}from"./iconify-BDcX0yw8.js";import{S as p}from"./scrollable-layout-CS-vPTNM.js";import{u as h}from"./use-double-check-CbbarqDt.js";import{d as x}from"./route-handle-DcEil3NY.js";const v=x({title:[{label:"Settings",to:"/settings"},{label:"Passkeys"}],iconNavActions:[{icon:"mdi:arrow-left",label:"Back to settings",to:"/settings",end:!0}]});function b({currentPasskeyId:i,passkey:s}){const t=u(),{doubleCheck:n,getButtonProps:r}=h(),l=s.id===i,o=t.state!=="idle",c=n?"mdi:help":"mdi:trash-can-outline",d=n?`Confirm revoke ${s.label}`:`Revoke ${s.label}`;return e.jsxs("li",{className:"flex min-h-11 items-center justify-between gap-3 border-b-2 border-black px-3 py-2",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx("span",{className:"inline-flex min-h-11 min-w-11 items-center justify-center","aria-hidden":"true",children:l?e.jsx(a,{className:"size-6",icon:"mdi:star"}):null}),e.jsx("span",{className:"truncate text-base",children:s.label})]}),e.jsxs(t.Form,{method:"post",children:[e.jsx("input",{name:"intent",type:"hidden",value:"revoke"}),e.jsx("input",{name:"passkeyId",type:"hidden",value:s.id}),e.jsx("button",{"aria-label":d,className:"inline-flex min-h-11 min-w-11 items-center justify-center disabled:opacity-25",disabled:o,type:"submit",...r(),children:e.jsx(a,{className:"size-6",icon:c})})]}),t.data?.error?e.jsx("p",{className:"sr-only",children:t.data.error}):null]})}const w=m(function({actionData:s,loaderData:t}){return e.jsx(p,{children:e.jsxs("section",{className:"space-y-6 pt-6",children:[e.jsxs("div",{className:"space-y-2 px-6 sm:px-8",children:[e.jsx("p",{className:"text-sm uppercase tracking-[0.08em]",children:"Active devices"}),e.jsx("p",{className:"text-base leading-6",children:"Revoke any passkey that should stop signing in. Revoking the device you are using will send you back through registration."})]}),s?.error?e.jsx("p",{className:"border-t-2 border-black pt-3 text-base leading-6",children:s.error}):null,e.jsx("ul",{className:"border-t-2 border-black",children:t.passkeys.map(n=>e.jsx(b,{currentPasskeyId:t.currentPasskeyId,passkey:n},n.id))})]})})});export{w as default,v as handle};
@@ -1 +0,0 @@
1
- import{w as m,d as g,L as p}from"./chunk-EPOLDU6W-B-j6nV8T.js";import{r as n,j as t}from"./files.browse-9p4xl9MV.js";import{a as b,S as x}from"./auth-shell-FEL5QDuP.js";import{r as h}from"./webauthn.client-C1ZSQfKs.js";import"./safe-area.module-CwCzHS6V.js";function v(){if(typeof navigator>"u")return"Unnamed device";const e=navigator.userAgent,i=/iPhone/i.test(e)?"iPhone":/iPad/i.test(e)?"iPad":/Android/i.test(e)?"Android":/Mac/i.test(e)?"Mac":/Windows/i.test(e)?"Windows":/Linux/i.test(e)?"Linux":null,a=/Edg/i.test(e)?"Edge":/Chrome|CriOS/i.test(e)?"Chrome":/Firefox|FxiOS/i.test(e)?"Firefox":/Safari/i.test(e)&&!/Chrome|CriOS|Edg/i.test(e)?"Safari":null;return i&&a?`${a} on ${i}`:i?`${i} device`:a?`${a} device`:"Unnamed device"}const P=m(function({}){const i=g(),a=n.useMemo(()=>v(),[]),[o,u]=n.useState(""),[r,l]=n.useState(!1),[d,c]=n.useState(null);async function f(){try{l(!0),c(null);const s=await h(o.trim()||a);i(s.redirectTo)}catch(s){c(s instanceof Error?s.message:"Passkey registration failed.")}finally{l(!1)}}return t.jsx(b,{title:"Register device",copy:"",children:t.jsxs("form",{className:"space-y-4",onSubmit:s=>{s.preventDefault(),f()},children:[t.jsxs("label",{className:"block space-y-3 py-1",children:[t.jsx("span",{className:"text-sm uppercase tracking-[0.08em]",children:"Device label"}),t.jsx("input",{className:"min-h-11 w-full bg-white px-0 py-2 text-base text-black focus-visible:outline-2 focus-visible:outline-black focus-visible:outline-offset-2",disabled:r,name:"label",onChange:s=>u(s.target.value),placeholder:"Desktop, iPhone, Macbook...",value:o})]}),t.jsx("button",{className:"inline-flex min-h-11 w-full items-center justify-center bg-black px-3 py-2 text-base text-white sm:w-auto",disabled:r,type:"submit",children:r?"Waiting for your device":"Register passkey"}),t.jsx(x,{message:d}),t.jsx("p",{className:"text-sm leading-6",children:t.jsx(p,{className:"underline underline-offset-4",to:"/login",children:"Login"})})]})})});export{P as default};
@@ -1 +0,0 @@
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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--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-outline-style:solid;--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:Charter, "Bitstream Charter", Georgia, serif;--font-mono:"Fira Code", ui-monospace, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.84375rem;--text-xs--line-height:1.125rem;--text-sm:.984375rem;--text-sm--line-height:1.40625rem;--text-base:1.125rem;--text-base--line-height:1.6875rem;--text-lg:1.26563rem;--text-lg--line-height:1.82813rem;--text-xl:1.40625rem;--text-xl--line-height:1.82813rem;--text-2xl:1.6875rem;--text-2xl--line-height:2.10938rem;--text-3xl:2.10938rem;--text-3xl--line-height:2.53125rem;--text-5xl:3.375rem;--text-5xl--line-height:1;--font-weight-bold:700;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-accent-dark-gray:#404040;--color-accent-mid-gray:gray;--color-accent-light-gray:silver;--color-accent-red:#ff6163;--color-accent-green:#00b036;--color-accent-navy:#000084;--color-accent-aqua:#00f0ff;--color-accent-violet:#e0f;--color-accent-orange:#fa0;--color-accent-lemon:#f0ff00;--color-accent-chartreuse:green;--color-accent-grape:#9338be;--color-accent-sky:#0af;--color-accent-orange-red:#f40}}@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,ui-sans-serif, system-ui, 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{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{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.top-1{top:calc(var(--spacing) * 1)}.right-0{right:calc(var(--spacing) * 0)}.right-1{right:calc(var(--spacing) * 1)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing) * 0)}.left-1\/2{left:50%}.left-8{left:calc(var(--spacing) * 8)}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-1{margin:calc(var(--spacing) * 1)}.m-2{margin:calc(var(--spacing) * 2)}.m-3{margin:calc(var(--spacing) * 3)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-auto{margin-inline:auto}.my-8{margin-block:calc(var(--spacing) * 8)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-auto{margin-top:auto}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-20{width:calc(var(--spacing) * 20);height:calc(var(--spacing) * 20)}.size-full{width:100%;height:100%}.h-9{height:calc(var(--spacing) * 9)}.h-full{height:100%}.max-h-72{max-height:calc(var(--spacing) * 72)}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-1\.5{min-height:calc(var(--spacing) * 1.5)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-11{min-height:calc(var(--spacing) * 11)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-dvh{min-height:100dvh}.min-h-full{min-height:100%}.w-3{width:calc(var(--spacing) * 3)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-10{width:calc(var(--spacing) * 10)}.w-80{width:calc(var(--spacing) * 80)}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[42rem\]{max-width:42rem}.max-w-max{max-width:max-content}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-9{min-width:calc(var(--spacing) * 9)}.min-w-11{min-width:calc(var(--spacing) * 11)}.min-w-\[min\(max-content\,calc\(100\%\/var\(--count\)\)\)\]{min-width:min(max-content,calc(100% / var(--count)))}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.flex-1{flex:1}.flex-\[1_1_0\]{flex:1 1 0}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.grid-cols-\[0\.5rem_minmax\(0\,1fr\)\]{grid-template-columns:.5rem minmax(0,1fr)}.grid-cols-\[1fr\]{grid-template-columns:1fr}.grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[auto_1fr_auto\]{grid-template-columns:auto 1fr auto}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_minmax\(14rem\,20rem\)_auto\]{grid-template-columns:minmax(0,1fr) minmax(14rem,20rem) auto}.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}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0{gap:calc(var(--spacing) * 0)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-0\.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)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * 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-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y-2{border-block-style:var(--tw-border-style);border-block-width:2px}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-black{border-color:var(--color-black)}.border-t-\[var\(--color-accent-green\)\]{border-top-color:var(--color-accent-green)}.border-t-\[var\(--color-accent-red\)\]{border-top-color:var(--color-accent-red)}.border-b-\[var\(--color-accent-green\)\]{border-bottom-color:var(--color-accent-green)}.border-b-\[var\(--color-accent-red\)\]{border-bottom-color:var(--color-accent-red)}.border-l-black{border-left-color:var(--color-black)}.bg-\[color-mix\(in_srgb\,var\(--color-accent-lemon\)_28\%\,white\)\]{background-color:#fbffb8}@supports (color:color-mix(in lab,red,red)){.bg-\[color-mix\(in_srgb\,var\(--color-accent-lemon\)_28\%\,white\)\]{background-color:color-mix(in srgb,var(--color-accent-lemon) 28%,white)}}.bg-\[var\(--color-accent-green\)\]{background-color:var(--color-accent-green)}.bg-\[var\(--color-accent-red\)\]{background-color:var(--color-accent-red)}.bg-black{background-color:var(--color-black)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/0{background-color:#0000}@supports (color:color-mix(in lab,red,red)){.bg-white\/0{background-color:color-mix(in oklab,var(--color-white) 0%,transparent)}}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing) * 0)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pr-1{padding-right:calc(var(--spacing) * 1)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-top{vertical-align:top}.font-\[\"Fira_Code\"\,ui-monospace\,monospace\]{font-family:Fira Code,ui-monospace,monospace}.font-\[Charter\,\"Bitstream_Charter\"\,Georgia\,serif\]{font-family:Charter,Bitstream Charter,Georgia,serif}.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-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--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-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.tracking-\[0\.05em\]{--tw-tracking:.05em;letter-spacing:.05em}.tracking-\[0\.08em\]{--tw-tracking:.08em;letter-spacing:.08em}.tracking-\[0\.12em\]{--tw-tracking:.12em;letter-spacing:.12em}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-black{color:var(--color-black)}.text-black\/60{color:#0009}@supports (color:color-mix(in lab,red,red)){.text-black\/60{color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow{--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)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);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,)}.grayscale{--tw-grayscale:grayscale(100%);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,)}.invert{--tw-invert:invert(100%);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,)}.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,)}.select-none{-webkit-user-select:none;user-select:none}.first\:pt-0:first-child{padding-top:calc(var(--spacing) * 0)}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing) * 0)}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-black:focus-visible{outline-color:var(--color-black)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-\[color-mix\(in_srgb\,var\(--color-accent-sky\)_14\%\,white\)\]:active{background-color:#dbf3ff}@supports (color:color-mix(in lab,red,red)){.active\:bg-\[color-mix\(in_srgb\,var\(--color-accent-sky\)_14\%\,white\)\]:active{background-color:color-mix(in srgb,var(--color-accent-sky) 14%,white)}}.disabled\:opacity-25:disabled{opacity:.25}@media(min-width:40rem){.sm\:w-auto{width:auto}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.md\:items-center{align-items:center}.md\:justify-end{justify-content:flex-end}}}html,body{color:#000;background:#fff;min-height:100%;margin:0;font-family:Charter,Bitstream Charter,Georgia,serif}body{min-height:100dvh}button,input,textarea{font:inherit}::selection{color:#fff;background:#000}:focus-visible{outline-offset:2px;outline:2px solid #000}@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-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@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-outline-style{syntax:"*";inherits:false;initial-value:solid}@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}
@@ -1 +0,0 @@
1
- import{w as i,v as o,M as c,x as l,S as p,y as m,O as x,i as u,F as d}from"./chunk-EPOLDU6W-B-j6nV8T.js";import{r as f,j as e}from"./files.browse-9p4xl9MV.js";import{s as h}from"./safe-area.module-CwCzHS6V.js";const j="/sw.js";function g(){return f.useEffect(()=>{"serviceWorker"in navigator&&navigator.serviceWorker.register(j)},[]),null}const y=()=>[{rel:"manifest",href:"/manifest.webmanifest"},{rel:"icon",href:"/favicon.ico",sizes:"any"},{rel:"icon",href:"/icon.svg",type:"image/svg+xml"},{rel:"icon",href:"/icon-192.png",type:"image/png",sizes:"192x192"},{rel:"icon",href:"/icon-512.png",type:"image/png",sizes:"512x512"},{rel:"apple-touch-icon",href:"/apple-touch-icon.png",sizes:"180x180"}];function E({children:s}){return e.jsxs("html",{lang:"en",children:[e.jsxs("head",{children:[e.jsx("meta",{charSet:"utf-8"}),e.jsx("meta",{name:"viewport",content:"width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"}),e.jsx("meta",{content:"#ffffff",name:"theme-color"}),e.jsx("meta",{content:"yes",name:"apple-mobile-web-app-capable"}),e.jsx("meta",{content:"default",name:"apple-mobile-web-app-status-bar-style"}),e.jsx("meta",{content:"Scriptorium",name:"apple-mobile-web-app-title"}),e.jsx(c,{}),e.jsx(l,{})]}),e.jsxs("body",{children:[s,e.jsx(p,{}),e.jsx(g,{}),e.jsx(m,{})]})]})}const k=i(function(){return e.jsx(x,{})}),R=o(function({error:t}){let r="Oops!",a="An unexpected error occurred.",n;return u(t)&&(r=t.status===404?"404":"Error",a=t.status===404?"The requested page could not be found.":t.statusText||a),e.jsxs("main",{className:`${h.pageShell} min-h-dvh bg-white text-black`,children:[e.jsxs("div",{className:"mx-auto flex max-w-3xl flex-col gap-6 border-t-2 border-black pt-8",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"text-sm uppercase tracking-[0.08em]",children:"Scriptorium"}),e.jsx("h1",{className:"text-3xl font-bold",children:r}),e.jsx("p",{className:"max-w-2xl text-base leading-6",children:a})]}),e.jsx(d,{action:"/logout",method:"post",children:e.jsx("button",{className:"min-h-11 bg-black px-3 py-2 text-base text-white",type:"submit",children:"Clear session"})})]}),n]})});export{R as ErrorBoundary,E as Layout,k as default,y as links};
@@ -1 +0,0 @@
1
- import{r,j as c}from"./files.browse-9p4xl9MV.js";const m=24;function E({children:i,footer:u,header:a,stickToBottom:n=!1}){const s=r.useRef(null),f=r.useRef(!1),t=r.useRef(!0),l=r.useCallback(()=>{const e=s.current;if(!e){t.current=!0;return}const d=e.scrollHeight-e.scrollTop-e.clientHeight;t.current=d<=m},[]),o=r.useCallback(()=>{const e=s.current;e&&(e.scrollTop=e.scrollHeight,t.current=!0,f.current=!0)},[]);return r.useEffect(()=>{if(!n)return;const e=s.current;if(e)return l(),e.addEventListener("scroll",l),()=>e.removeEventListener("scroll",l)},[n,l]),r.useEffect(()=>{if(!n||f.current&&!t.current)return;const e=window.requestAnimationFrame(o);return()=>window.cancelAnimationFrame(e)},[i,o,n]),r.useEffect(()=>{if(!n)return;const e=()=>{t.current&&window.requestAnimationFrame(o)};return window.addEventListener("resize",e),window.visualViewport?.addEventListener("resize",e),()=>{window.removeEventListener("resize",e),window.visualViewport?.removeEventListener("resize",e)}},[o,n]),c.jsxs("div",{className:"flex min-h-0 flex-1 flex-col",children:[a?c.jsx("div",{className:"border-b-2 border-black px-6 sm:px-8",children:a}):null,c.jsx("div",{className:"flex min-h-0 flex-1 flex-col overflow-y-auto",ref:s,children:i}),u?c.jsx("footer",{className:"mt-auto",children:u}):null]})}export{E as S};
@@ -1 +0,0 @@
1
- function t(e){return e?.title?.trim()||e?.id?.slice(0,12)||"Session"}function s(e){return[{label:e.instanceName?.trim()||"Instance",...e.instanceId?{to:`/instances/${e.instanceId}`}:{}},{label:t(e.session),...e.instanceId&&e.sessionId?{to:`/instances/${e.instanceId}/sessions/${e.sessionId}`}:{}}]}function o(e,i){const n=`/instances/${e}/sessions/${i}`;return[{icon:"mdi:message-outline",label:"Chat transcript",to:n,end:!0},{icon:"mdi:source-branch",label:"Git view",to:`${n}/git`,end:!0},{icon:"mdi:file-document-multiple-outline",label:"Files view",to:`${n}/files`,end:!0}]}export{o as a,s as g};
@@ -1 +0,0 @@
1
- import{r as a,j as m}from"./files.browse-9p4xl9MV.js";import{a as w,n as E,s as R,l as h,u as C}from"./instance-events-provider-DTYnvumj.js";import{i as k,t as g}from"./sidebar-DRhB33rT.js";const D=w({type:h("session.read"),instanceId:R(),sessionId:R(),lastReadAt:E()}),b=a.createContext(null);function P(e,s){return s?!(s.instanceId&&s.instanceId!==e.instanceId||s.sessionId&&s.sessionId!==e.sessionId):!0}function J({children:e}){const s=a.useRef(null),r=a.useRef(new Map),o=a.useRef(0),i=a.useCallback(d=>{for(const u of r.current.values())P(d,u.filter)&&u.handler(d)},[]);a.useEffect(()=>{const d=new EventSource("/session-read-status/events");return s.current=d,d.onmessage=u=>{try{const c=D.safeParse(JSON.parse(u.data));if(!c.success){console.error("[session read events] Event did not match its schema.",{error:c.error.format(),raw:u.data});return}i(c.data)}catch(c){console.error("[session read events] Failed to parse SSE payload.",{error:c,raw:u.data})}},()=>{d.close(),s.current=null}},[i]);const l=a.useCallback((d,u)=>{const c=o.current;return o.current+=1,r.current.set(c,{filter:u,handler:d}),()=>{r.current.delete(c)}},[]),f=a.useMemo(()=>({subscribe:l}),[l]);return m.jsx(b.Provider,{value:f,children:e})}function j(e,s){const r=a.useContext(b),o=a.useRef(e);o.current=e,a.useEffect(()=>{if(!r)throw new Error("useReadStatusEvents must be used within a ReadStatusEventsProvider.");return r.subscribe(i=>o.current(i),s)},[r,s?.instanceId,s?.sessionId])}const A=a.createContext(null),S=a.createContext(null),v=a.createContext(null);function y(e,s){return s}function I(e){switch(e.type){case"session.created":case"session.updated":return e.properties.info.time.updated??e.properties.info.time.created;case"message.updated":return e.properties.info.time.created;case"message.part.updated":{const s="time"in e.properties.part?e.properties.part.time:void 0;if(s&&typeof s=="object"){const r="start"in s?s.start:null,o="end"in s?s.end:null;return typeof o=="number"?o:typeof r=="number"?r:Date.now()}return Date.now()}default:return Date.now()}}function M(e,s){switch(s.type){case"reset":return s.sessions;case"mark-read":{const r=e[s.sessionId];return!r||r.lastReadAt!==null&&r.lastReadAt>=s.lastReadAt?e:{...e,[s.sessionId]:{...r,lastReadAt:s.lastReadAt}}}case"update":{const r=e[s.sessionId],o=s.state;if(o===null){if(!(s.sessionId in e))return e;const{[s.sessionId]:l,...f}=e;return f}const i={id:s.sessionId,title:r?.title??null,directory:r?.directory??null,createdAt:r?.createdAt??null,updatedAt:r?.updatedAt??null,lastReadAt:r?.lastReadAt??null,...o};return r?.title===i.title&&r?.directory===i.directory&&r?.createdAt===i.createdAt&&r?.updatedAt===i.updatedAt&&r?.lastReadAt===i.lastReadAt?e:{...e,[s.sessionId]:i}}}}function N({children:e,initialSessions:s}){const[r,o]=a.useReducer(M,s),i=a.useRef(new Map),l=a.useRef(0);a.useEffect(()=>{o({type:"reset",sessions:s})},[s]);const f=a.useCallback((t,n)=>{const p=l.current;return l.current+=1,i.current.set(p,{filter:n,handler:t}),()=>{i.current.delete(p)}},[]),d=a.useCallback(t=>{for(const n of i.current.values())n.filter?.instanceId&&n.filter.instanceId!==t.instanceId||n.filter?.sessionId&&n.filter.sessionId!==t.sessionId||n.handler(t)},[]),u=a.useCallback((t,n,p=Date.now())=>{o({type:"mark-read",sessionId:y(t,n),lastReadAt:p})},[]),c=a.useMemo(()=>({subscribe:f}),[f]),x=a.useMemo(()=>({markReadOptimistic:u}),[u]);return j(t=>{o({type:"update",sessionId:t.sessionId,state:{lastReadAt:t.lastReadAt}})}),C(t=>{switch(t.type){case"session.deleted":o({type:"update",sessionId:t.properties.info.id,state:null});return;case"session.created":case"session.updated":{const n=I(t);o({type:"update",sessionId:t.properties.info.id,state:{...g(t.properties.info),updatedAt:n}}),d({instanceId:t.instanceId,sessionId:t.properties.info.id,updatedAt:n});return}case"message.updated":{const n=I(t);o({type:"update",sessionId:t.properties.info.sessionID,state:{updatedAt:n}}),d({instanceId:t.instanceId,sessionId:t.properties.info.sessionID,updatedAt:n});return}case"message.part.updated":{const n=I(t);o({type:"update",sessionId:t.properties.part.sessionID,state:{updatedAt:n}}),d({instanceId:t.instanceId,sessionId:t.properties.part.sessionID,updatedAt:n});return}case"message.part.delta":case"message.part.removed":case"permission.asked":{const n=I(t);o({type:"update",sessionId:t.properties.sessionID,state:{updatedAt:n}}),d({instanceId:t.instanceId,sessionId:t.properties.sessionID,updatedAt:n});return}case"session.error":{const n=t.properties.sessionID;if(!n)return;const p=I(t);o({type:"update",sessionId:n,state:{updatedAt:p}}),d({instanceId:t.instanceId,sessionId:n,updatedAt:p})}}},{types:["session.created","session.updated","session.deleted","message.updated","message.part.updated","message.part.delta","message.part.removed","permission.asked","session.error"]}),m.jsx(S.Provider,{value:x,children:m.jsx(v.Provider,{value:c,children:m.jsx(A.Provider,{value:r,children:e})})})}function O(){const e=a.useContext(A);if(!e)throw new Error("useSessions must be used within a SessionsProvider.");return e}function U(e){return O()[e]??null}function T(e,s){const r=U(y(e,s));return r?k(r,r.lastReadAt):!1}function q(){const e=a.useContext(S);if(!e)throw new Error("useMarkSessionReadOptimistic must be used within a SessionsProvider.");return e.markReadOptimistic}export{J as R,N as S,q as a,O as b,U as c,T as u};
@@ -1 +0,0 @@
1
- import{w as e,L as t}from"./chunk-EPOLDU6W-B-j6nV8T.js";import{j as s}from"./files.browse-9p4xl9MV.js";import{S as a}from"./scrollable-layout-CS-vPTNM.js";import{d as n}from"./route-handle-DcEil3NY.js";const p=n({title:[{label:"Settings"}]}),m=e(function(){return s.jsx(a,{children:s.jsxs("section",{className:"space-y-4 pt-6",children:[s.jsx("p",{className:"px-6 text-sm uppercase tracking-[0.08em] sm:px-8",children:"Sections"}),s.jsxs(t,{className:"block min-h-11 border-l-4 border-black px-3 py-2 text-base font-bold",to:"/settings/passkeys",children:[s.jsx("span",{className:"block",children:"Passkeys"}),s.jsx("span",{className:"block text-sm leading-6 opacity-60",children:"Review active passkeys and revoke devices that should no longer sign in."})]})]})})});export{m as default,p as handle};
@@ -1 +0,0 @@
1
- const i=3;function S(t){return{id:t.id,title:t.title??null,directory:t.directory??null,createdAt:t.time?.created??null,updatedAt:t.time?.updated??null}}function n(t){return t.updatedAt??t.createdAt??0}function o(t,e){const r=n(t);return r<=0?!1:e===null?!0:r>e}function u(t){return[...t].sort((e,r)=>n(r)-n(e))}function c(t,e=Date.now()){return u(t).filter(r=>{const s=n(r);return s>0&&e-s<=6048e5})}export{i as S,c as f,o as i,u as s,S as t};
@@ -1 +0,0 @@
1
- import{r as c}from"./files.browse-9p4xl9MV.js";function r(...o){return(...e)=>o.forEach(t=>t?.(...e))}function k(o=5e3){const[e,t]=c.useState(!1);c.useEffect(()=>{if(!e)return;const n=window.setTimeout(()=>{t(!1)},o);return()=>{window.clearTimeout(n)}},[e,o]);function l(n){const s=()=>{t(!1)},f=e?void 0:u=>{u.preventDefault(),t(!0)},i=u=>{u.key==="Escape"&&t(!1)};return{...n,onBlur:r(s,n?.onBlur),onClick:r(f,n?.onClick),onKeyUp:r(i,n?.onKeyUp)}}return{doubleCheck:e,getButtonProps:l}}export{k as u};
@@ -1,422 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { homedir, platform } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
5
- import { parseArgs } from "node:util";
6
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
7
- import { z } from "zod";
8
- const CONFIG_FILE_NAME = "config.yml";
9
- const SECRETS_FILE_NAME = "secrets.yml";
10
- const PRIVATE_FILE_MODE = 0o600;
11
- const DOCUMENTATION_HOME = "$HOME";
12
- const DOCUMENTATION_DATA_DIR = "<scriptorium_data_dir>";
13
- let cachedRuntimeConfiguration = null;
14
- function meta(schema, metadata) {
15
- return schema.meta(metadata);
16
- }
17
- function section(shape) {
18
- return z.object(shape).prefault(() => Object.fromEntries(Object.keys(shape).map((key) => [key, undefined])));
19
- }
20
- function getOptionMetadata(schema) {
21
- const metadata = schema.meta();
22
- if (!metadata?.description) {
23
- throw new Error("Missing required option metadata.");
24
- }
25
- return metadata;
26
- }
27
- function override(schema, sources) {
28
- const metadata = getOptionMetadata(schema);
29
- return z.transform((configValue) => {
30
- if (metadata.cli) {
31
- const cliValue = sources.cli?.[metadata.cli];
32
- if (cliValue !== undefined) {
33
- return cliValue;
34
- }
35
- }
36
- if (metadata.env) {
37
- const envValue = sources.env?.[metadata.env];
38
- if (envValue !== undefined) {
39
- return envValue;
40
- }
41
- }
42
- return configValue;
43
- }).pipe(schema);
44
- }
45
- function defaultConfigDirectory(env = process.env) {
46
- const home = env.HOME?.trim() || homedir();
47
- switch (platform()) {
48
- case "darwin":
49
- return resolve(home, "Library/Application Support/scriptorium");
50
- case "win32":
51
- return resolve(env.APPDATA?.trim() || join(home, "AppData/Roaming"), "scriptorium");
52
- default:
53
- return resolve(env.XDG_DATA_HOME?.trim() || join(home, ".local/share"), "scriptorium");
54
- }
55
- }
56
- export function getRuntimeConfigPaths(sources = {}) {
57
- const directory = resolve(sources.configDir ||
58
- sources.env?.SCRIPTORIUM_CONFIG_DIR?.trim() ||
59
- defaultConfigDirectory(sources.env));
60
- return {
61
- directory,
62
- configFile: join(directory, CONFIG_FILE_NAME),
63
- secretsFile: join(directory, SECRETS_FILE_NAME),
64
- };
65
- }
66
- function getDefaultDatabasePath(sources) {
67
- return join(getRuntimeConfigPaths(sources).directory, "app.db");
68
- }
69
- function createConfigSchema(sources = {}) {
70
- const defaultHomeDirectory = sources.env?.HOME?.trim() || homedir();
71
- return z.object({
72
- server: section({
73
- host: override(meta(z.string().min(1).default("0.0.0.0"), {
74
- cli: "host",
75
- env: "HOST",
76
- description: "Host interface for the web server.",
77
- }), sources),
78
- port: override(meta(z.coerce.number().int().min(1).max(65535).default(5174), {
79
- cli: "port",
80
- env: "PORT",
81
- description: "Port for the web server.",
82
- }), sources),
83
- }),
84
- workspace: section({
85
- browserRoot: override(meta(z.string().min(1).default(defaultHomeDirectory), {
86
- cli: "browser-root",
87
- env: "SCRIPTORIUM_BROWSER_ROOT",
88
- description: "Root directory exposed in the workspace browser.",
89
- }), sources),
90
- }),
91
- opencode: section({
92
- bin: override(meta(z.string().min(1).default("opencode"), {
93
- cli: "opencode-bin",
94
- env: "OPENCODE_BIN",
95
- description: "OpenCode executable name or path.",
96
- }), sources),
97
- }),
98
- network: section({
99
- tailscale: override(meta(z.coerce.boolean().default(false), {
100
- cli: "tailscale",
101
- description: "Expose the app with tailscale serve.",
102
- }), sources),
103
- }),
104
- database: section({
105
- path: override(meta(z.string().min(1).default(getDefaultDatabasePath(sources)), {
106
- cli: "db-path",
107
- env: "SCRIPTORIUM_DB_PATH",
108
- description: "Path to the SQLite database file.",
109
- }), sources),
110
- }),
111
- });
112
- }
113
- function createSecretsSchema(sources = {}) {
114
- return z.object({
115
- auth: section({
116
- sessionSecret: override(meta(z.string().min(32).optional(), {
117
- env: "SESSION_SECRET",
118
- description: "Session signing secret.",
119
- }), sources),
120
- }),
121
- });
122
- }
123
- function readYamlFile(filePath) {
124
- if (!existsSync(filePath)) {
125
- return {};
126
- }
127
- const content = readFileSync(filePath, "utf8");
128
- const parsed = parseYaml(content);
129
- if (parsed === null || parsed === undefined) {
130
- return {};
131
- }
132
- if (typeof parsed !== "object" || Array.isArray(parsed)) {
133
- throw new Error(`Expected ${filePath} to contain a YAML mapping.`);
134
- }
135
- return parsed;
136
- }
137
- function writeYamlFile(filePath, value) {
138
- mkdirSync(dirname(filePath), { recursive: true });
139
- const content = stringifyYaml(value);
140
- writeFileSync(filePath, content, { encoding: "utf8", mode: PRIVATE_FILE_MODE });
141
- try {
142
- chmodSync(filePath, PRIVATE_FILE_MODE);
143
- }
144
- catch {
145
- // Best-effort only. Some platforms ignore chmod semantics.
146
- }
147
- }
148
- function ensureSessionSecret(parsedSecrets, paths, sources) {
149
- const configuredSecret = parsedSecrets.auth.sessionSecret;
150
- if (configuredSecret) {
151
- return {
152
- auth: {
153
- sessionSecret: configuredSecret,
154
- },
155
- };
156
- }
157
- const generatedSecret = randomBytes(32).toString("hex");
158
- const persistedSecrets = {
159
- auth: {
160
- sessionSecret: generatedSecret,
161
- },
162
- };
163
- if (sources.env?.SESSION_SECRET === undefined) {
164
- writeYamlFile(paths.secretsFile, persistedSecrets);
165
- }
166
- return persistedSecrets;
167
- }
168
- export function resolveRuntimeConfiguration(sources = {}) {
169
- const paths = getRuntimeConfigPaths(sources);
170
- const rawConfig = readYamlFile(paths.configFile);
171
- const rawSecrets = readYamlFile(paths.secretsFile);
172
- const config = createConfigSchema(sources).parse(rawConfig);
173
- const secrets = ensureSessionSecret(createSecretsSchema(sources).parse(rawSecrets), paths, sources);
174
- return { config, secrets, paths };
175
- }
176
- export function getRuntimeConfiguration() {
177
- if (!cachedRuntimeConfiguration) {
178
- cachedRuntimeConfiguration = resolveRuntimeConfiguration({ env: process.env });
179
- }
180
- return cachedRuntimeConfiguration;
181
- }
182
- export function initializeRuntimeConfiguration(sources = {}) {
183
- cachedRuntimeConfiguration = resolveRuntimeConfiguration(sources);
184
- return cachedRuntimeConfiguration;
185
- }
186
- export function resetRuntimeConfigurationCache() {
187
- cachedRuntimeConfiguration = null;
188
- }
189
- function unwrapSchema(schema) {
190
- if (schema instanceof z.ZodDefault) {
191
- return unwrapSchema(schema._def.innerType);
192
- }
193
- if (schema instanceof z.ZodPrefault) {
194
- return unwrapSchema(schema._def.innerType);
195
- }
196
- if (schema instanceof z.ZodPipe) {
197
- return unwrapSchema(schema._def.out);
198
- }
199
- if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
200
- return unwrapSchema(schema._def.innerType);
201
- }
202
- return schema;
203
- }
204
- function getDefaultValue(schema) {
205
- if (schema instanceof z.ZodDefault) {
206
- return schema._def.defaultValue;
207
- }
208
- if (schema instanceof z.ZodPipe) {
209
- return getDefaultValue(schema._def.out);
210
- }
211
- if (schema instanceof z.ZodPrefault) {
212
- return getDefaultValue(schema._def.innerType);
213
- }
214
- return undefined;
215
- }
216
- function getTypeName(schema) {
217
- const unwrapped = unwrapSchema(schema);
218
- if (unwrapped instanceof z.ZodString) {
219
- return "string";
220
- }
221
- if (unwrapped instanceof z.ZodNumber) {
222
- return "number";
223
- }
224
- if (unwrapped instanceof z.ZodBoolean) {
225
- return "boolean";
226
- }
227
- if (unwrapped instanceof z.ZodEnum) {
228
- return "enum";
229
- }
230
- return "unknown";
231
- }
232
- export function collectSchemaDocumentation(schema, path = []) {
233
- const unwrapped = unwrapSchema(schema);
234
- if (unwrapped instanceof z.ZodObject) {
235
- return Object.entries(unwrapped.shape).flatMap(([key, child]) => collectSchemaDocumentation(child, [...path, key]));
236
- }
237
- const metadata = schema instanceof z.ZodPipe
238
- ? getOptionMetadata(schema._def.out)
239
- : getOptionMetadata(schema);
240
- return [{
241
- path,
242
- type: getTypeName(schema),
243
- description: metadata.description,
244
- cli: metadata.cli ? `--${metadata.cli}` : undefined,
245
- env: metadata.env,
246
- defaultValue: getDefaultValue(schema),
247
- }];
248
- }
249
- export function getRuntimeConfigurationDocumentation() {
250
- const sources = {
251
- env: {
252
- APPDATA: "C:/Users/you/AppData/Roaming",
253
- HOME: "/path/to/home",
254
- XDG_DATA_HOME: "/path/to/home/.local/share",
255
- },
256
- cli: {},
257
- };
258
- return {
259
- config: collectSchemaDocumentation(createConfigSchema(sources)),
260
- secrets: collectSchemaDocumentation(createSecretsSchema(sources)),
261
- };
262
- }
263
- function getCliRows() {
264
- return getRuntimeConfigurationDocumentation().config.filter((row) => row.cli);
265
- }
266
- function getCliOptionsConfig() {
267
- const options = {
268
- help: {
269
- type: "boolean",
270
- },
271
- "config-dir": {
272
- type: "string",
273
- },
274
- };
275
- for (const row of getCliRows()) {
276
- const flag = row.cli.slice(2);
277
- options[flag] = {
278
- type: row.type === "boolean" ? "boolean" : "string",
279
- };
280
- if (row.type === "boolean") {
281
- options[`no-${flag}`] = {
282
- type: "boolean",
283
- };
284
- }
285
- }
286
- return options;
287
- }
288
- export function parseRuntimeCliArgs(args) {
289
- const parsed = parseArgs({
290
- args,
291
- options: getCliOptionsConfig(),
292
- strict: true,
293
- allowPositionals: false,
294
- });
295
- const values = parsed.values;
296
- const cli = {};
297
- for (const row of getCliRows()) {
298
- const flag = row.cli.slice(2);
299
- const value = values[flag];
300
- if (value !== undefined) {
301
- cli[flag] = value;
302
- continue;
303
- }
304
- if (row.type === "boolean" && values[`no-${flag}`] === true) {
305
- cli[flag] = false;
306
- }
307
- }
308
- return {
309
- cli,
310
- configDir: typeof values["config-dir"] === "string"
311
- ? values["config-dir"]
312
- : undefined,
313
- help: values.help === true,
314
- };
315
- }
316
- function stringifyDefaultValue(value) {
317
- if (value === undefined) {
318
- return "";
319
- }
320
- return `\`${String(normalizeDocumentationValue(value))}\``;
321
- }
322
- function normalizeDocumentationValue(value) {
323
- if (typeof value !== "string") {
324
- return value;
325
- }
326
- return value
327
- .replaceAll("/path/to/home/Library/Application Support/scriptorium", DOCUMENTATION_DATA_DIR)
328
- .replaceAll("/path/to/home/.local/share/scriptorium", DOCUMENTATION_DATA_DIR)
329
- .replaceAll("C:/Users/you/AppData/Roaming/scriptorium", DOCUMENTATION_DATA_DIR)
330
- .replaceAll("/path/to/home", DOCUMENTATION_HOME);
331
- }
332
- function escapeTableCell(value) {
333
- return (value || "").replaceAll("|", "\\|");
334
- }
335
- function setNestedValue(target, path, value) {
336
- let current = target;
337
- for (const segment of path.slice(0, -1)) {
338
- const existing = current[segment];
339
- if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
340
- current[segment] = {};
341
- }
342
- current = current[segment];
343
- }
344
- current[path.at(-1)] = value;
345
- }
346
- function getExampleValue(row, section) {
347
- if (row.defaultValue !== undefined) {
348
- return normalizeDocumentationValue(row.defaultValue);
349
- }
350
- if (section === "secrets") {
351
- return "<generated on first run or set via env>";
352
- }
353
- return `<set ${row.path.join(".")}>`;
354
- }
355
- export function buildDocumentationExample(rows, section) {
356
- const result = {};
357
- for (const row of rows) {
358
- setNestedValue(result, row.path, getExampleValue(row, section));
359
- }
360
- return stringifyYaml(result).trim();
361
- }
362
- function renderDocumentationTable(rows) {
363
- const lines = [
364
- "| Key | Type | Default | CLI | Env | Description |",
365
- "| --- | --- | --- | --- | --- | --- |",
366
- ];
367
- for (const row of rows) {
368
- lines.push(`| \`${row.path.join(".")}\` | ${row.type} | ${escapeTableCell(stringifyDefaultValue(row.defaultValue))} | ${escapeTableCell(row.cli ? `\`${row.cli}\`` : "")} | ${escapeTableCell(row.env ? `\`${row.env}\`` : "")} | ${escapeTableCell(row.description)} |`);
369
- }
370
- return lines.join("\n");
371
- }
372
- export function renderRuntimeConfigurationMarkdown() {
373
- const documentation = getRuntimeConfigurationDocumentation();
374
- return [
375
- "# Configuration Reference",
376
- "",
377
- "Scriptorium reads non-sensitive settings from `config.yml` and secrets from `secrets.yml` in its per-user config directory.",
378
- "CLI flags override environment variables, which override YAML values, which override schema defaults.",
379
- "",
380
- `| Platform | ${DOCUMENTATION_DATA_DIR} |`,
381
- "| --- | --- |",
382
- "| macOS | `~/Library/Application Support/scriptorium` |",
383
- "| Linux | `$XDG_DATA_HOME/scriptorium` or `~/.local/share/scriptorium` |",
384
- "| Windows | `%APPDATA%\\scriptorium` |",
385
- "",
386
- `Examples use \`${DOCUMENTATION_DATA_DIR}\` as shorthand for Scriptorium's per-user data directory and \`${DOCUMENTATION_HOME}\` for the user's home directory.`,
387
- "",
388
- "## config.yml",
389
- "",
390
- "```yaml",
391
- buildDocumentationExample(documentation.config, "config"),
392
- "```",
393
- "",
394
- renderDocumentationTable(documentation.config),
395
- "",
396
- "## secrets.yml",
397
- "",
398
- "```yaml",
399
- buildDocumentationExample(documentation.secrets, "secrets"),
400
- "```",
401
- "",
402
- renderDocumentationTable(documentation.secrets),
403
- "",
404
- ].join("\n");
405
- }
406
- export function renderRuntimeConfigurationHelp() {
407
- const rows = getCliRows();
408
- return [
409
- "Usage: scriptorium [options]",
410
- "",
411
- "Options:",
412
- " --help Show this help message",
413
- " --config-dir <path> Use an alternate config directory",
414
- ...rows.map((row) => {
415
- const flag = row.cli;
416
- const typeSuffix = row.type === "boolean" ? "" : ` <${row.type}>`;
417
- const negated = row.type === "boolean" ? `, --no-${flag.slice(2)}` : "";
418
- const env = row.env ? ` [env: ${row.env}]` : "";
419
- return ` ${flag}${typeSuffix}${negated} ${row.description}${env}`;
420
- }),
421
- ].join("\n");
422
- }
@@ -1,21 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { initializeRuntimeConfiguration, parseRuntimeCliArgs, renderRuntimeConfigurationHelp, } from "../app/lib/runtime-config.server.js";
4
- import { serveProductionApp } from "./serve.js";
5
- function getPackageRoot() {
6
- return path.resolve(fileURLToPath(new URL("../../..", import.meta.url)));
7
- }
8
- export async function main(args = process.argv.slice(2)) {
9
- const parsedCli = parseRuntimeCliArgs(args);
10
- if (parsedCli.help) {
11
- process.stdout.write(`${renderRuntimeConfigurationHelp()}\n`);
12
- return;
13
- }
14
- const runtime = initializeRuntimeConfiguration({
15
- cli: parsedCli.cli,
16
- configDir: parsedCli.configDir,
17
- env: process.env,
18
- });
19
- await serveProductionApp(runtime, getPackageRoot());
20
- }
21
- await main();
@@ -1,101 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { pathToFileURL } from "node:url";
6
- import compression from "compression";
7
- import express from "express";
8
- import morgan from "morgan";
9
- import { createRequestHandler } from "@react-router/express";
10
- function getBuildPaths(packageRoot) {
11
- return {
12
- packageRoot,
13
- clientDirectory: path.join(packageRoot, "build/client"),
14
- clientAssetsDirectory: path.join(packageRoot, "build/client/assets"),
15
- serverBuildPath: path.join(packageRoot, "build/server/index.js"),
16
- };
17
- }
18
- function assertBuildExists(paths) {
19
- if (!existsSync(paths.serverBuildPath) || !existsSync(paths.clientDirectory)) {
20
- throw new Error("Scriptorium has not been built yet. Run `npm run build` before `npm start`, or use the published package with `npx scriptorium`.");
21
- }
22
- }
23
- async function loadServerBuild(serverBuildPath) {
24
- return import(pathToFileURL(serverBuildPath).href);
25
- }
26
- function startTailscale(port) {
27
- try {
28
- execSync(`tailscale serve --bg http://localhost:${port}`, { stdio: "inherit" });
29
- }
30
- catch {
31
- // tailscale not available - ignore
32
- }
33
- }
34
- function stopTailscale() {
35
- try {
36
- execSync("tailscale serve --https=443 off", { stdio: "ignore" });
37
- }
38
- catch {
39
- // tailscale not available - ignore
40
- }
41
- }
42
- export async function serveProductionApp(runtime, packageRoot) {
43
- const buildPaths = getBuildPaths(packageRoot);
44
- assertBuildExists(buildPaths);
45
- process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
46
- const build = await loadServerBuild(buildPaths.serverBuildPath);
47
- const app = express();
48
- const port = runtime.config.server.port;
49
- const host = runtime.config.server.host;
50
- app.disable("x-powered-by");
51
- app.use(compression());
52
- app.use("/assets", express.static(buildPaths.clientAssetsDirectory, {
53
- immutable: true,
54
- maxAge: "1y",
55
- }));
56
- app.use(express.static(buildPaths.clientDirectory));
57
- app.use(morgan("tiny"));
58
- app.all("*", createRequestHandler({
59
- build,
60
- mode: process.env.NODE_ENV,
61
- }));
62
- if (runtime.config.network.tailscale) {
63
- startTailscale(port);
64
- }
65
- const server = await new Promise((resolve, reject) => {
66
- const httpServer = app.listen(port, host, () => resolve(httpServer));
67
- httpServer.once("error", reject);
68
- });
69
- const networkAddress = Object.values(os.networkInterfaces())
70
- .flat()
71
- .find((entry) => String(entry?.family).includes("4") && !entry?.internal)?.address;
72
- if (networkAddress) {
73
- process.stdout.write(`[scriptorium] http://${host}:${port} (http://${networkAddress}:${port})\n`);
74
- }
75
- else {
76
- process.stdout.write(`[scriptorium] http://${host}:${port}\n`);
77
- }
78
- let shuttingDown = false;
79
- const shutdown = () => {
80
- if (shuttingDown) {
81
- return;
82
- }
83
- shuttingDown = true;
84
- if (runtime.config.network.tailscale) {
85
- stopTailscale();
86
- }
87
- server.close((error) => {
88
- if (error) {
89
- process.stderr.write(`${error.message}\n`);
90
- process.exitCode = 1;
91
- }
92
- });
93
- };
94
- server.once("close", () => {
95
- if (runtime.config.network.tailscale) {
96
- stopTailscale();
97
- }
98
- });
99
- process.once("SIGINT", shutdown);
100
- process.once("SIGTERM", shutdown);
101
- }