aisubs 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 +21 -0
- package/README.md +629 -0
- package/dashboard/public/aisubs-mark.svg +11 -0
- package/dist/account-key.d.ts +2 -0
- package/dist/account-key.js +11 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +477 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +97 -0
- package/dist/dashboard/aisubs-mark.svg +11 -0
- package/dist/dashboard/assets/index-BMoILzPw.js +64 -0
- package/dist/dashboard/assets/index-DHqDdNVe.css +2 -0
- package/dist/dashboard/index.html +15 -0
- package/dist/dashboard/logos/anthropic.svg +3 -0
- package/dist/dashboard/logos/github-copilot.svg +3 -0
- package/dist/dashboard/logos/google.svg +3 -0
- package/dist/dashboard/logos/openai.svg +3 -0
- package/dist/dashboard/logos/opencode-dark.svg +18 -0
- package/dist/dashboard/logos/opencode-light.svg +18 -0
- package/dist/dashboard/logos/xai.svg +3 -0
- package/dist/dashboard.d.ts +18 -0
- package/dist/dashboard.js +139 -0
- package/dist/http.d.ts +22 -0
- package/dist/http.js +263 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/providers/chatgpt.d.ts +7 -0
- package/dist/providers/chatgpt.js +402 -0
- package/dist/providers/claude.d.ts +7 -0
- package/dist/providers/claude.js +289 -0
- package/dist/providers/copilot.d.ts +6 -0
- package/dist/providers/copilot.js +466 -0
- package/dist/providers/grok.d.ts +7 -0
- package/dist/providers/grok.js +215 -0
- package/dist/providers/opencode.d.ts +4 -0
- package/dist/providers/opencode.js +147 -0
- package/dist/store.d.ts +18 -0
- package/dist/store.js +121 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +5 -0
- package/dist/usage.js +418 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +68 -0
- package/examples/direct.mjs +38 -0
- package/examples/server.mjs +20 -0
- package/package.json +122 -0
- package/public/aisubs-chatgpt-account.png +0 -0
- package/public/aisubs-copilot-account.png +0 -0
- package/public/aisubs-dashboard.png +0 -0
- package/public/aisubs-grok-account.png +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-sky-300:oklch(82.8% .111 230.318);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:0}.inset-x-\[14px\]{inset-inline:14px}.top-3{top:calc(var(--spacing) * 3)}.top-\[14px\]{top:14px}.right-2\.5{right:calc(var(--spacing) * 2.5)}.right-3{right:calc(var(--spacing) * 3)}.z-20{z-index:20}.z-30{z-index:30}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.m-0{margin:0}.m-\[-18px\]{margin:-18px}.mx-auto{margin-inline:auto}.my-3{margin-block:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-\[18px\]{margin-top:18px}.mt-auto{margin-top:auto}.mt-px{margin-top:1px}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-\[18px\]{margin-bottom:18px}.mb-\[22px\]{margin-bottom:22px}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-\[30px\]{width:30px;height:30px}.size-\[38px\]{width:38px;height:38px}.size-\[54px\]{width:54px;height:54px}.size-\[58px\]{width:58px;height:58px}.h-3{height:calc(var(--spacing) * 3)}.h-10{height:calc(var(--spacing) * 10)}.h-44{height:calc(var(--spacing) * 44)}.h-\[3px\]{height:3px}.h-\[30px\]{height:30px}.h-\[34px\]{height:34px}.h-full{height:100%}.max-h-\[92dvh\]{max-height:92dvh}.max-h-\[510px\]{max-height:510px}.min-h-6{min-height:calc(var(--spacing) * 6)}.min-h-7{min-height:calc(var(--spacing) * 7)}.min-h-8{min-height:calc(var(--spacing) * 8)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-\[43px\]{min-height:43px}.min-h-\[54px\]{min-height:54px}.min-h-\[58px\]{min-height:58px}.min-h-\[60px\]{min-height:60px}.min-h-\[390px\]{min-height:390px}.min-h-\[420px\]{min-height:420px}.min-h-dvh{min-height:100dvh}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-\[330px\]{max-width:330px}.max-w-\[390px\]{max-width:390px}.max-w-\[420px\]{max-width:420px}.max-w-\[560px\]{max-width:560px}.max-w-\[630px\]{max-width:630px}.max-w-\[1180px\]{max-width:1180px}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-center{align-items:center}.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}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-7{gap:calc(var(--spacing) * 7)}.gap-\[18px\]{gap:18px}.gap-px{gap:1px}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.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-zinc-200>:not(:last-child)){border-color:var(--color-zinc-200)}.justify-self-end{justify-self:flex-end}.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-\[10px\]{border-radius:10px}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-amber-600\/40{border-color:#dd740066}@supports (color:color-mix(in lab, red, red)){.border-amber-600\/40{border-color:color-mix(in oklab, var(--color-amber-600) 40%, transparent)}}.border-blue-600\/40{border-color:#155dfc66}@supports (color:color-mix(in lab, red, red)){.border-blue-600\/40{border-color:color-mix(in oklab, var(--color-blue-600) 40%, transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab, red, red)){.border-emerald-500\/40{border-color:color-mix(in oklab, var(--color-emerald-500) 40%, transparent)}}.border-emerald-600\/40{border-color:#00976766}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/40{border-color:color-mix(in oklab, var(--color-emerald-600) 40%, transparent)}}.border-orange-500\/40{border-color:#fe6e0066}@supports (color:color-mix(in lab, red, red)){.border-orange-500\/40{border-color:color-mix(in oklab, var(--color-orange-500) 40%, transparent)}}.border-red-500\/30{border-color:#fb2c364d}@supports (color:color-mix(in lab, red, red)){.border-red-500\/30{border-color:color-mix(in oklab, var(--color-red-500) 30%, transparent)}}.border-red-600\/40{border-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.border-red-600\/40{border-color:color-mix(in oklab, var(--color-red-600) 40%, transparent)}}.border-stone-400\/40{border-color:#a6a09b66}@supports (color:color-mix(in lab, red, red)){.border-stone-400\/40{border-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent)}}.border-transparent{border-color:#0000}.border-violet-500\/40{border-color:#8d54ff66}@supports (color:color-mix(in lab, red, red)){.border-violet-500\/40{border-color:color-mix(in oklab, var(--color-violet-500) 40%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.border-white\/20{border-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-200\/80{border-color:#e4e4e7cc}@supports (color:color-mix(in lab, red, red)){.border-zinc-200\/80{border-color:color-mix(in oklab, var(--color-zinc-200) 80%, transparent)}}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400\/40{border-color:#9f9fa966}@supports (color:color-mix(in lab, red, red)){.border-zinc-400\/40{border-color:color-mix(in oklab, var(--color-zinc-400) 40%, transparent)}}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-500\/10{background-color:#f99c001a}@supports (color:color-mix(in lab, red, red)){.bg-amber-500\/10{background-color:color-mix(in oklab, var(--color-amber-500) 10%, transparent)}}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab, red, red)){.bg-blue-500\/10{background-color:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.bg-current{background-color:currentColor}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/10{background-color:color-mix(in oklab, var(--color-emerald-500) 10%, transparent)}}.bg-orange-500\/10{background-color:#fe6e001a}@supports (color:color-mix(in lab, red, red)){.bg-orange-500\/10{background-color:color-mix(in oklab, var(--color-orange-500) 10%, transparent)}}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/10{background-color:color-mix(in oklab, var(--color-red-500) 10%, transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-stone-500\/10{background-color:#79716b1a}@supports (color:color-mix(in lab, red, red)){.bg-stone-500\/10{background-color:color-mix(in oklab, var(--color-stone-500) 10%, transparent)}}.bg-transparent{background-color:#0000}.bg-violet-500\/10{background-color:#8d54ff1a}@supports (color:color-mix(in lab, red, red)){.bg-violet-500\/10{background-color:color-mix(in oklab, var(--color-violet-500) 10%, transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.bg-white\/5{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.bg-white\/80{background-color:#fffc}@supports (color:color-mix(in lab, red, red)){.bg-white\/80{background-color:color-mix(in oklab, var(--color-white) 80%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-100\/50{background-color:#f4f4f580}@supports (color:color-mix(in lab, red, red)){.bg-zinc-100\/50{background-color:color-mix(in oklab, var(--color-zinc-100) 50%, transparent)}}.bg-zinc-100\/70{background-color:#f4f4f5b3}@supports (color:color-mix(in lab, red, red)){.bg-zinc-100\/70{background-color:color-mix(in oklab, var(--color-zinc-100) 70%, transparent)}}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-500\/10{background-color:#71717b1a}@supports (color:color-mix(in lab, red, red)){.bg-zinc-500\/10{background-color:color-mix(in oklab, var(--color-zinc-500) 10%, transparent)}}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950\/35{background-color:#09090b59}@supports (color:color-mix(in lab, red, red)){.bg-zinc-950\/35{background-color:color-mix(in oklab, var(--color-zinc-950) 35%, transparent)}}.\[mask-image\:url\(\'\/logos\/anthropic\.svg\'\)\]{-webkit-mask-image:url(/logos/anthropic.svg);mask-image:url(/logos/anthropic.svg)}.\[mask-image\:url\(\'\/logos\/github-copilot\.svg\'\)\]{-webkit-mask-image:url(/logos/github-copilot.svg);mask-image:url(/logos/github-copilot.svg)}.\[mask-image\:url\(\'\/logos\/openai\.svg\'\)\]{-webkit-mask-image:url(/logos/openai.svg);mask-image:url(/logos/openai.svg)}.\[mask-image\:url\(\'\/logos\/xai\.svg\'\)\]{-webkit-mask-image:url(/logos/xai.svg);mask-image:url(/logos/xai.svg)}.\[mask-size\:contain\]{-webkit-mask-size:contain;mask-size:contain}.\[mask-position\:center\]{-webkit-mask-position:50%;mask-position:50%}.\[mask-repeat\:no-repeat\]{-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-3\.5{padding:calc(var(--spacing) * 3.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-9{padding:calc(var(--spacing) * 9)}.p-\[18px\]{padding:18px}.p-\[22px\]{padding:22px}.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-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[18px\]{padding-inline:18px}.py-0{padding-block:0}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-24{padding-top:calc(var(--spacing) * 24)}.pt-\[62px\]{padding-top:62px}.pt-\[106px\]{padding-top:106px}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-\[18px\]{padding-right:18px}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pl-1{padding-left:var(--spacing)}.pl-3{padding-left:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.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))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[19px\]{font-size:19px}.text-\[clamp\(26px\,3vw\,36px\)\]{font-size:clamp(26px,3vw,36px)}.text-\[clamp\(28px\,3vw\,36px\)\]{font-size:clamp(28px,3vw,36px)}.leading-\[1\.05\]{--tw-leading:1.05;line-height:1.05}.leading-\[1\.7\]{--tw-leading:1.7;line-height:1.7}.leading-\[1\.08\]{--tw-leading:1.08;line-height:1.08}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[-0\.05em\]{--tw-tracking:-.05em;letter-spacing:-.05em}.tracking-\[-0\.045em\]{--tw-tracking:-.045em;letter-spacing:-.045em}.tracking-\[0\.1em\]{--tw-tracking:.1em;letter-spacing:.1em}.tracking-\[0\.05em\]{--tw-tracking:.05em;letter-spacing:.05em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.wrap-break-word{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.\[tab-size\:2\]{tab-size:2}.text-amber-300{color:var(--color-amber-300)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-blue-700{color:var(--color-blue-700)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-300\/70{color:#5ee9b5b3}@supports (color:color-mix(in lab, red, red)){.text-emerald-300\/70{color:color-mix(in oklab, var(--color-emerald-300) 70%, transparent)}}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-orange-600{color:var(--color-orange-600)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-sky-300{color:var(--color-sky-300)}.text-violet-600{color:var(--color-violet-600)}.text-white{color:var(--color-white)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.uppercase{text-transform:uppercase}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.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)}.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,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{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))}.outline-none{--tw-outline-style:none;outline-style:none}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.placeholder\:text-zinc-500::placeholder{color:var(--color-zinc-500)}@media (hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:-translate-y-px:hover{--tw-translate-y:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:border-red-500\/50:hover{border-color:#fb2c3680}@supports (color:color-mix(in lab, red, red)){.hover\:border-red-500\/50:hover{border-color:color-mix(in oklab, var(--color-red-500) 50%, transparent)}}.hover\:border-white\/30:hover{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/30:hover{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.hover\:border-zinc-200:hover{border-color:var(--color-zinc-200)}.hover\:border-zinc-300:hover{border-color:var(--color-zinc-300)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-zinc-50:hover{color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:text-zinc-950:hover{color:var(--color-zinc-950)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.focus\:border-zinc-500:focus{border-color:var(--color-zinc-500)}.focus\:ring-3:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-zinc-300\/50:focus{--tw-ring-color:#d4d4d880}@supports (color:color-mix(in lab, red, red)){.focus\:ring-zinc-300\/50:focus{--tw-ring-color:color-mix(in oklab, var(--color-zinc-300) 50%, transparent)}}.active\:translate-y-px:active{--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.aria-pressed\:bg-zinc-900[aria-pressed=true]{background-color:var(--color-zinc-900)}.aria-pressed\:text-zinc-50[aria-pressed=true]{color:var(--color-zinc-50)}.aria-selected\:border-zinc-100[aria-selected=true]{border-color:var(--color-zinc-100)}.aria-selected\:text-zinc-50[aria-selected=true]{color:var(--color-zinc-50)}@media (width>=40rem){.sm\:inset-x-\[18px\]{inset-inline:18px}.sm\:top-4{top:calc(var(--spacing) * 4)}.sm\:col-auto{grid-column:auto}.sm\:row-auto{grid-row:auto}.sm\:mb-7{margin-bottom:calc(var(--spacing) * 7)}.sm\:inline{display:inline}.sm\:max-h-\[calc\(100dvh-48px\)\]{max-height:calc(100dvh - 48px)}.sm\:w-\[220px\]{width:220px}.sm\:w-auto{width:auto}.sm\:min-w-\[190px\]{min-width:190px}.sm\:flex-none{flex:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-\[minmax\(0\,1fr\)_160px\]{grid-template-columns:minmax(0,1fr) 160px}.sm\:grid-cols-\[minmax\(0\,1fr\)_auto_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto auto}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:rounded-xl{border-radius:var(--radius-xl)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:pt-\[22px\]{padding-top:22px}.sm\:pr-\[116px\]{padding-right:116px}.sm\:pb-16{padding-bottom:calc(var(--spacing) * 16)}}@media (width>=48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@media (width>=64rem){.lg\:grid-cols-\[minmax\(250px\,\.36fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(250px,.36fr) minmax(0,1fr)}}.dark\:block:where(.dark,.dark *){display:block}.dark\:hidden:where(.dark,.dark *){display:none}:where(.dark\:divide-zinc-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-zinc-800)}.dark\:border-stone-500\/40:where(.dark,.dark *){border-color:#79716b66}@supports (color:color-mix(in lab, red, red)){.dark\:border-stone-500\/40:where(.dark,.dark *){border-color:color-mix(in oklab, var(--color-stone-500) 40%, transparent)}}.dark\:border-white\/20:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/20:where(.dark,.dark *){border-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.dark\:border-zinc-700:where(.dark,.dark *){border-color:var(--color-zinc-700)}.dark\:border-zinc-800:where(.dark,.dark *){border-color:var(--color-zinc-800)}.dark\:bg-stone-400\/10:where(.dark,.dark *){background-color:#a6a09b1a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-stone-400) 10%, transparent)}}.dark\:bg-white\/5:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/5:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.dark\:bg-zinc-100:where(.dark,.dark *){background-color:var(--color-zinc-100)}.dark\:bg-zinc-700:where(.dark,.dark *){background-color:var(--color-zinc-700)}.dark\:bg-zinc-800:where(.dark,.dark *){background-color:var(--color-zinc-800)}.dark\:bg-zinc-800\/40:where(.dark,.dark *){background-color:#27272a66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-800\/40:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-800) 40%, transparent)}}.dark\:bg-zinc-800\/50:where(.dark,.dark *){background-color:#27272a80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-800\/50:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-800) 50%, transparent)}}.dark\:bg-zinc-900:where(.dark,.dark *){background-color:var(--color-zinc-900)}.dark\:bg-zinc-900\/80:where(.dark,.dark *){background-color:#18181bcc}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-900\/80:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-900) 80%, transparent)}}.dark\:bg-zinc-950:where(.dark,.dark *){background-color:var(--color-zinc-950)}.dark\:text-amber-300:where(.dark,.dark *){color:var(--color-amber-300)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-emerald-300:where(.dark,.dark *){color:var(--color-emerald-300)}.dark\:text-orange-300:where(.dark,.dark *){color:var(--color-orange-300)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-400:where(.dark,.dark *){color:var(--color-red-400)}.dark\:text-violet-300:where(.dark,.dark *){color:var(--color-violet-300)}.dark\:text-zinc-50:where(.dark,.dark *){color:var(--color-zinc-50)}.dark\:text-zinc-100:where(.dark,.dark *){color:var(--color-zinc-100)}.dark\:text-zinc-300:where(.dark,.dark *){color:var(--color-zinc-300)}.dark\:text-zinc-400:where(.dark,.dark *){color:var(--color-zinc-400)}.dark\:text-zinc-900:where(.dark,.dark *){color:var(--color-zinc-900)}.dark\:invert-0:where(.dark,.dark *){--tw-invert:invert(0%);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,)}.dark\:placeholder\:text-zinc-400:where(.dark,.dark *)::placeholder{color:var(--color-zinc-400)}@media (hover:hover){.dark\:hover\:border-zinc-600:where(.dark,.dark *):hover{border-color:var(--color-zinc-600)}.dark\:hover\:border-zinc-700:where(.dark,.dark *):hover{border-color:var(--color-zinc-700)}.dark\:hover\:bg-zinc-300:where(.dark,.dark *):hover{background-color:var(--color-zinc-300)}.dark\:hover\:bg-zinc-800:where(.dark,.dark *):hover{background-color:var(--color-zinc-800)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-zinc-50:where(.dark,.dark *):hover{color:var(--color-zinc-50)}}.dark\:focus\:border-zinc-500:where(.dark,.dark *):focus{border-color:var(--color-zinc-500)}.dark\:focus\:ring-zinc-700:where(.dark,.dark *):focus{--tw-ring-color:var(--color-zinc-700)}.dark\:aria-pressed\:bg-zinc-100:where(.dark,.dark *)[aria-pressed=true]{background-color:var(--color-zinc-100)}.dark\:aria-pressed\:text-zinc-900:where(.dark,.dark *)[aria-pressed=true]{color:var(--color-zinc-900)}.\[\&_dd\]\:m-0 dd{margin:0}.\[\&_dd\]\:text-\[10px\] dd{font-size:10px}.\[\&_dd\]\:text-\[11px\] dd{font-size:11px}.\[\&_dd\]\:font-semibold dd{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_dd\]\:wrap-break-word dd{overflow-wrap:break-word}.\[\&_dt\]\:mb-1 dt{margin-bottom:var(--spacing)}.\[\&_dt\]\:text-\[9px\] dt{font-size:9px}.\[\&_dt\]\:font-bold dt{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_dt\]\:tracking-\[0\.05em\] dt{--tw-tracking:.05em;letter-spacing:.05em}.\[\&_dt\]\:text-zinc-500 dt{color:var(--color-zinc-500)}.\[\&_dt\]\:uppercase dt{text-transform:uppercase}.dark\:\[\&_dt\]\:text-zinc-400:where(.dark,.dark *) dt{color:var(--color-zinc-400)}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}.\[\&\>div\]\:min-w-0>div{min-width:0}.\[\&\>div\]\:border-t>div{border-top-style:var(--tw-border-style);border-top-width:1px}.\[\&\>div\]\:border-zinc-200>div{border-color:var(--color-zinc-200)}.\[\&\>div\]\:p-4>div{padding:calc(var(--spacing) * 4)}.dark\:\[\&\>div\]\:border-zinc-800:where(.dark,.dark *)>div{border-color:var(--color-zinc-800)}@media (width>=40rem){.\[\&\>div\:nth-child\(-n\+2\)\]\:sm\:border-t-0>div:nth-child(-n+2){border-top-style:var(--tw-border-style);border-top-width:0}.\[\&\>div\:nth-child\(even\)\]\:sm\:border-l>div:nth-child(2n){border-left-style:var(--tw-border-style);border-left-width:1px}.\[\&\>div\:nth-child\(even\)\]\:sm\:border-zinc-200>div:nth-child(2n){border-color:var(--color-zinc-200)}.dark\:\[\&\>div\:nth-child\(even\)\]\:sm\:border-zinc-800:where(.dark,.dark *)>div:nth-child(2n){border-color:var(--color-zinc-800)}.\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-t>div:nth-child(n+3){border-top-style:var(--tw-border-style);border-top-width:1px}.\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-zinc-200>div:nth-child(n+3){border-color:var(--color-zinc-200)}.dark\:\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-zinc-800:where(.dark,.dark *)>div:nth-child(n+3){border-color:var(--color-zinc-800)}.\[\&\>div\:nth-child\(odd\)\]\:sm\:border-r>div:nth-child(odd){border-right-style:var(--tw-border-style);border-right-width:1px}}}@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-divide-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-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@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}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
7
|
+
<meta name="theme-color" content="#181818" />
|
|
8
|
+
<title>AI Subs</title>
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BMoILzPw.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DHqDdNVe.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M26.9568 9.88184H22.1265L30.7753 31.7848H35.4917L26.9568 9.88184ZM13.028 9.88184L4.4917 31.7848H9.32203L11.2305 27.1793H20.2166L22.0126 31.6724H26.8444L18.0832 9.88184H13.028ZM12.5783 23.1361L15.4987 15.3853L18.5315 23.1361H12.5783Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M35.3993 26.4544C34.2871 28.3855 27.8314 32.9425 20 32.9425C12.1686 32.9425 5.71288 28.3855 4.60075 26.4544C4.5427 26.3532 4.50826 26.2401 4.5 26.1237V22.418C4.51199 22.3209 4.53496 22.2256 4.56846 22.1338C5.04896 20.9261 6.30833 19.1733 7.93325 18.7031C8.14896 18.149 8.468 17.3404 8.76508 16.7437C8.71727 16.2776 8.69485 15.8094 8.69792 15.3409C8.69792 13.6217 9.06217 12.113 10.1601 10.9906C10.6729 10.4662 11.3097 10.0644 12.064 9.76091C13.871 8.29357 16.4453 7.05745 19.9716 7.05745C23.4978 7.05745 26.129 8.29357 27.936 9.76091C28.6903 10.0644 29.3271 10.4662 29.8399 10.9906C30.9378 12.113 31.3021 13.6217 31.3021 15.3409C31.3021 15.8162 31.284 16.2877 31.2349 16.7437C31.532 17.3404 31.851 18.149 32.0667 18.7031C33.6917 19.1733 34.951 20.9261 35.4315 22.1338C35.4677 22.2248 35.4908 22.3205 35.5 22.418V26.1237C35.4917 26.2401 35.4573 26.3532 35.3993 26.4544ZM20.2222 18.7147H19.7778C19.6422 18.945 19.4889 19.1644 19.3193 19.3709C18.3247 20.5941 16.8419 21.2981 14.7881 21.2981C12.56 21.2981 10.9273 20.8344 9.90304 19.6719C9.86495 19.6283 9.82833 19.5835 9.79325 19.5375L9.66667 19.6719V28.1775C11.5202 29.1837 15.4972 30.992 20 30.992C24.5028 30.992 28.4798 29.1837 30.3333 28.1775V19.6719L30.2067 19.5375C30.2067 19.5375 30.1641 19.5957 30.097 19.6719C29.0727 20.8344 27.44 21.2981 25.2119 21.2981C23.1581 21.2981 21.6753 20.5941 20.6807 19.3709C20.5111 19.1644 20.3578 18.945 20.2222 18.7147ZM23.2292 22.9127C23.9383 22.9127 24.5208 23.4952 24.5208 24.2043V26.7877C24.5208 27.4968 23.9383 28.0793 23.2292 28.0793C22.52 28.0793 21.9375 27.4968 21.9375 26.7877V24.2043C21.9375 23.4952 22.52 22.9127 23.2292 22.9127ZM16.7708 22.9127C17.48 22.9127 18.0625 23.4952 18.0625 24.2043V26.7877C18.0625 27.4968 17.48 28.0793 16.7708 28.0793C16.0617 28.0793 15.4792 27.4968 15.4792 26.7877V24.2043C15.4792 23.4952 16.0617 22.9127 16.7708 22.9127ZM21.0501 14.9237C21.2258 16.289 21.5707 17.3947 22.1842 18.149C22.7551 18.8517 23.649 19.3606 25.2119 19.3606C27.2437 19.3606 28.1724 18.9253 28.6438 18.3905C29.1398 17.8287 29.3646 16.9051 29.3646 15.3409C29.3646 13.8684 29.0507 12.9552 28.454 12.3455C27.8378 11.7152 26.7502 11.2321 24.8063 11.0216C22.8856 10.8136 21.975 11.1998 21.5345 11.7049C21.187 12.1014 20.97 12.7485 20.9688 13.7431V13.7702C20.9688 14.1121 20.9959 14.4966 21.0501 14.9237ZM18.9499 14.9237C19.0041 14.4966 19.0312 14.1117 19.0312 13.7689V13.7431C19.03 12.7485 18.813 12.1014 18.4655 11.7049C18.025 11.1998 17.1144 10.8136 15.1937 11.0216C13.2498 11.2321 12.1622 11.7152 11.546 12.3455C10.9493 12.9552 10.6354 13.8684 10.6354 15.3409C10.6354 16.9051 10.8615 17.8287 11.3562 18.3905C11.8276 18.9253 12.7563 19.3606 14.7881 19.3606C16.351 19.3606 17.2449 18.8517 17.8158 18.149C18.4293 17.3947 18.7742 16.2898 18.9499 14.9237Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M37 20.034C27.8809 20.5837 20.5808 27.8809 20.0326 37H19.966C19.4163 27.8809 12.1177 20.5837 3 20.034V19.9674C12.1191 19.4163 19.4163 12.1191 19.966 3H20.0326C20.5822 12.1191 27.8809 19.4163 37 19.9674V20.034Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M32.8377 17.282C33.2127 16.25 33.3072 15.218 33.2127 14.1875C33.1197 13.1571 32.7447 12.1251 32.2752 11.1876C31.4322 9.78209 30.2127 8.6571 28.8072 8.0001C27.3072 7.34461 25.7127 7.15711 24.1197 7.53211C23.3698 6.78212 22.5253 6.12512 21.5878 5.65713C20.6503 5.18913 19.5253 5.00013 18.4948 5.00013C16.8851 4.99074 15.3125 5.48246 13.9948 6.40712C12.6824 7.34311 11.7449 8.6571 11.2754 10.1571C10.1504 10.4376 9.21289 10.9071 8.27539 11.4696C7.4324 12.1251 6.77541 12.9696 6.21291 13.8126C5.36992 15.2195 5.08792 16.8125 5.27542 18.407C5.46399 19.9968 6.11605 21.496 7.1504 22.718C6.79608 23.7086 6.66795 24.7659 6.77541 25.8124C6.86991 26.8444 7.2449 27.8749 7.7129 28.8124C8.55739 30.2194 9.77538 31.3444 11.1824 31.9999C12.6824 32.6569 14.2753 32.8444 15.8698 32.4694C16.6198 33.2194 17.4628 33.8749 18.4003 34.3444C19.3378 34.8139 20.4628 34.9999 21.4948 34.9999C23.1043 35.0097 24.6769 34.5185 25.9947 33.5944C27.3072 32.6569 28.2447 31.3444 28.7127 29.8444C29.7719 29.6432 30.7682 29.1934 31.6197 28.5319C32.4627 27.8749 33.2127 27.1249 33.6822 26.1874C34.5251 24.7819 34.8071 23.1875 34.6196 21.5945C34.4322 20 33.8697 18.5015 32.8377 17.282ZM21.5878 33.0304C20.0878 33.0304 18.9628 32.5609 17.9323 31.7179C17.9323 31.7179 18.0253 31.6234 18.1198 31.6234L24.1197 28.1554C24.2862 28.0803 24.4196 27.9469 24.4947 27.7804C24.5698 27.636 24.6021 27.4731 24.5877 27.3109V18.875L27.1197 20.375V27.3124C27.1455 28.0547 27.0215 28.7945 26.755 29.4878C26.4885 30.181 26.085 30.8134 25.5687 31.3473C25.0523 31.8811 24.4337 32.3054 23.7497 32.5949C23.0658 32.8843 22.3305 33.0314 21.5878 33.0304ZM9.49488 27.8749C8.83789 26.7499 8.55739 25.4374 8.83789 24.125C8.83789 24.125 8.93239 24.2195 9.02539 24.2195L15.0253 27.6874C15.1693 27.7638 15.3325 27.7966 15.4948 27.7819C15.6823 27.7819 15.8698 27.7819 15.9628 27.6874L23.2753 23.4695V26.3749L17.1823 29.9374C16.5506 30.3042 15.8527 30.5427 15.1287 30.6393C14.4046 30.7358 13.6686 30.6884 12.9629 30.4999C11.4629 30.1249 10.2449 29.1874 9.49488 27.8749ZM7.9004 14.8445C8.56239 13.7234 9.58826 12.8627 10.8074 12.4056V19.532C10.8074 19.718 10.8074 19.907 10.9004 20C10.9755 20.1665 11.1089 20.2998 11.2754 20.375L18.5878 24.5944L16.0573 26.0944L10.0574 22.625C9.41842 22.2639 8.85742 21.7797 8.40684 21.2004C7.95627 20.6211 7.62506 19.9582 7.4324 19.25C7.05741 17.8445 7.1504 16.157 7.9004 14.8445ZM28.6197 19.625L21.3073 15.407L23.8377 13.9071L29.8377 17.375C30.7752 17.9375 31.5252 18.6875 31.9947 19.625C32.4642 20.5625 32.7447 21.5945 32.6502 22.7195C32.5603 23.7755 32.1699 24.7837 31.5252 25.6249C30.8697 26.4694 30.0252 27.1249 28.9947 27.4999V20.375C28.9947 20.1875 28.9947 20 28.9002 19.907C28.9002 19.907 28.8072 19.718 28.6197 19.625ZM31.1502 15.875C31.1502 15.875 31.0572 15.782 30.9627 15.782L24.9627 12.3126C24.7752 12.2196 24.6822 12.2196 24.4947 12.2196C24.3072 12.2196 24.1197 12.2196 24.0252 12.3126L16.7128 16.532V13.6251L22.8073 10.0626C23.7448 9.50009 24.7752 9.31259 25.9002 9.31259C26.9322 9.31259 27.9627 9.68759 28.9002 10.3446C29.7447 11.0001 30.4947 11.8446 30.8697 12.7821C31.2447 13.7196 31.3377 14.8445 31.1502 15.875ZM15.4003 21.125L12.8699 19.625V12.5946C12.8699 11.5626 13.1503 10.4376 13.7128 9.59459C14.2753 8.6571 15.1198 8.0001 16.0573 7.53211C17.0127 7.05249 18.0956 6.88812 19.1503 7.06261C20.1823 7.15711 21.2128 7.62511 22.0573 8.2821C22.0573 8.2821 21.9628 8.3751 21.8698 8.3751L15.8698 11.8446C15.7033 11.9197 15.57 12.0531 15.4948 12.2196C15.4003 12.4071 15.4003 12.5001 15.4003 12.6876V21.125ZM16.7128 18.125L19.9948 16.25L23.2753 18.125V21.875L19.9948 23.75L16.7128 21.875V18.125Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g transform="translate(30, 0)">
|
|
3
|
+
<g clip-path="url(#clip0_1401_86283)">
|
|
4
|
+
<mask id="mask0_1401_86283" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
|
|
5
|
+
<path d="M240 0H0V300H240V0Z" fill="white"/>
|
|
6
|
+
</mask>
|
|
7
|
+
<g mask="url(#mask0_1401_86283)">
|
|
8
|
+
<path d="M180 240H60V120H180V240Z" fill="#4B4646"/>
|
|
9
|
+
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#F1ECEC"/>
|
|
10
|
+
</g>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
<defs>
|
|
14
|
+
<clipPath id="clip0_1401_86283">
|
|
15
|
+
<rect width="240" height="300" fill="white"/>
|
|
16
|
+
</clipPath>
|
|
17
|
+
</defs>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g transform="translate(30, 0)">
|
|
3
|
+
<g clip-path="url(#clip0_1401_86274)">
|
|
4
|
+
<mask id="mask0_1401_86274" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
|
|
5
|
+
<path d="M240 0H0V300H240V0Z" fill="white"/>
|
|
6
|
+
</mask>
|
|
7
|
+
<g mask="url(#mask0_1401_86274)">
|
|
8
|
+
<path d="M180 240H60V120H180V240Z" fill="#CFCECD"/>
|
|
9
|
+
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#211E1E"/>
|
|
10
|
+
</g>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
<defs>
|
|
14
|
+
<clipPath id="clip0_1401_86274">
|
|
15
|
+
<rect width="240" height="300" fill="white"/>
|
|
16
|
+
</clipPath>
|
|
17
|
+
</defs>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M12.4579 15.6036L26.1529 35H20.0656L6.37059 15.6036H12.4579ZM12.4524 26.3764L15.4974 30.6909L12.4551 35H6.36377L12.4524 26.3764ZM33.6365 7.15727V35H28.647V14.2236L33.6365 7.15727ZM33.6365 5L20.0656 24.2205L17.0206 19.9073L27.5451 5H33.6365Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import type { SubscriptionAuth } from "./auth.js";
|
|
3
|
+
export interface SubscriptionAuthDashboardOptions {
|
|
4
|
+
auth: SubscriptionAuth;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
host?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
/** Maximum buffered proxy request body. Defaults to 10 MiB. */
|
|
9
|
+
maxProxyBodyBytes?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SubscriptionAuthDashboardServer {
|
|
12
|
+
server: Server;
|
|
13
|
+
apiKey: string;
|
|
14
|
+
url: string;
|
|
15
|
+
bootstrapUrl: string;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createSubscriptionAuthDashboardServer(options: SubscriptionAuthDashboardOptions): Promise<SubscriptionAuthDashboardServer>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { handleSubscriptionAuthApi, routeSegments, sendJson } from "./http.js";
|
|
7
|
+
import { errorMessage, urlHost } from "./utils.js";
|
|
8
|
+
const ASSET_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), "dashboard");
|
|
9
|
+
const CONTENT_TYPES = {
|
|
10
|
+
".css": "text/css; charset=utf-8",
|
|
11
|
+
".html": "text/html; charset=utf-8",
|
|
12
|
+
".js": "text/javascript; charset=utf-8",
|
|
13
|
+
".svg": "image/svg+xml",
|
|
14
|
+
".woff2": "font/woff2",
|
|
15
|
+
};
|
|
16
|
+
function sameSecret(actual, expected) {
|
|
17
|
+
if (!actual)
|
|
18
|
+
return false;
|
|
19
|
+
const left = Buffer.from(actual);
|
|
20
|
+
const right = Buffer.from(expected);
|
|
21
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
22
|
+
}
|
|
23
|
+
function cookie(request, name) {
|
|
24
|
+
for (const part of request.headers.cookie?.split(";") ?? []) {
|
|
25
|
+
const [key, ...value] = part.trim().split("=");
|
|
26
|
+
if (key === name)
|
|
27
|
+
return decodeURIComponent(value.join("="));
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function secure(response) {
|
|
32
|
+
response.setHeader("content-security-policy", "default-src 'self'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'");
|
|
33
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
34
|
+
response.setHeader("cross-origin-opener-policy", "same-origin");
|
|
35
|
+
response.setHeader("cross-origin-resource-policy", "same-origin");
|
|
36
|
+
response.setHeader("permissions-policy", "camera=(), geolocation=(), microphone=()");
|
|
37
|
+
response.setHeader("x-content-type-options", "nosniff");
|
|
38
|
+
response.setHeader("x-frame-options", "DENY");
|
|
39
|
+
}
|
|
40
|
+
export async function createSubscriptionAuthDashboardServer(options) {
|
|
41
|
+
const host = options.host ?? "127.0.0.1";
|
|
42
|
+
if (!["127.0.0.1", "::1", "localhost"].includes(host)) {
|
|
43
|
+
throw new Error("The AI Subs dashboard may only bind to localhost");
|
|
44
|
+
}
|
|
45
|
+
const origin = `http://${urlHost(host)}`;
|
|
46
|
+
const apiKey = options.apiKey ?? randomBytes(32).toString("base64url");
|
|
47
|
+
const bootstrapToken = randomBytes(32).toString("base64url");
|
|
48
|
+
const sessionToken = randomBytes(32).toString("base64url");
|
|
49
|
+
let bootstrapAvailable = true;
|
|
50
|
+
const server = createServer(async (request, response) => {
|
|
51
|
+
secure(response);
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(request.url ?? "/", origin);
|
|
54
|
+
if (url.pathname === "/health")
|
|
55
|
+
return sendJson(response, 200, { ok: true });
|
|
56
|
+
if (request.method === "GET" && url.pathname === "/bootstrap") {
|
|
57
|
+
if (!bootstrapAvailable ||
|
|
58
|
+
!sameSecret(url.searchParams.get("token") ?? undefined, bootstrapToken)) {
|
|
59
|
+
return sendJson(response, 401, { error: "Bootstrap link is invalid or has expired" });
|
|
60
|
+
}
|
|
61
|
+
bootstrapAvailable = false;
|
|
62
|
+
response.writeHead(302, {
|
|
63
|
+
location: "/",
|
|
64
|
+
"cache-control": "no-store",
|
|
65
|
+
"set-cookie": `aisubs_session=${encodeURIComponent(sessionToken)}; HttpOnly; SameSite=Strict; Path=/`,
|
|
66
|
+
});
|
|
67
|
+
response.end();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const bearer = request.headers.authorization?.startsWith("Bearer ")
|
|
71
|
+
? request.headers.authorization.slice(7)
|
|
72
|
+
: undefined;
|
|
73
|
+
const headerKey = Array.isArray(request.headers["x-api-key"])
|
|
74
|
+
? request.headers["x-api-key"][0]
|
|
75
|
+
: request.headers["x-api-key"];
|
|
76
|
+
const bearerAuthenticated = sameSecret(bearer, apiKey) || sameSecret(headerKey, apiKey);
|
|
77
|
+
const cookieAuthenticated = sameSecret(cookie(request, "aisubs_session"), sessionToken);
|
|
78
|
+
if (!bearerAuthenticated && !cookieAuthenticated) {
|
|
79
|
+
return sendJson(response, 401, { error: "Unauthorized" });
|
|
80
|
+
}
|
|
81
|
+
if (cookieAuthenticated &&
|
|
82
|
+
!bearerAuthenticated &&
|
|
83
|
+
!["GET", "HEAD", "OPTIONS"].includes(request.method ?? "GET") &&
|
|
84
|
+
request.headers.origin !== `http://${request.headers.host}`) {
|
|
85
|
+
return sendJson(response, 403, { error: "Cross-origin mutation blocked" });
|
|
86
|
+
}
|
|
87
|
+
if (["v1", "aisubs"].includes(routeSegments(url.pathname)[0] ?? "")) {
|
|
88
|
+
if (!(await handleSubscriptionAuthApi(options.auth, request, response, url, options.maxProxyBodyBytes))) {
|
|
89
|
+
sendJson(response, 404, { error: "Not found" });
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const requested = url.pathname === "/" ? "index.html" : url.pathname;
|
|
94
|
+
const assetPath = resolve(ASSET_DIRECTORY, `.${requested}`);
|
|
95
|
+
const assetRelative = relative(ASSET_DIRECTORY, assetPath);
|
|
96
|
+
if (assetRelative.startsWith("..") || isAbsolute(assetRelative)) {
|
|
97
|
+
return sendJson(response, 404, { error: "Not found" });
|
|
98
|
+
}
|
|
99
|
+
let file = assetPath;
|
|
100
|
+
let body = await readFile(file).catch(() => null);
|
|
101
|
+
if (!body || !extname(file)) {
|
|
102
|
+
file = join(ASSET_DIRECTORY, "index.html");
|
|
103
|
+
body = await readFile(file);
|
|
104
|
+
}
|
|
105
|
+
response.writeHead(200, {
|
|
106
|
+
"content-type": CONTENT_TYPES[extname(file)] ?? "application/octet-stream",
|
|
107
|
+
"cache-control": relative(ASSET_DIRECTORY, file).startsWith("assets/")
|
|
108
|
+
? "public, max-age=31536000, immutable"
|
|
109
|
+
: "no-store",
|
|
110
|
+
});
|
|
111
|
+
response.end(body);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (response.headersSent)
|
|
115
|
+
response.destroy();
|
|
116
|
+
else
|
|
117
|
+
sendJson(response, 400, { error: errorMessage(error) });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
await new Promise((resolve, reject) => {
|
|
121
|
+
server.once("error", reject);
|
|
122
|
+
server.listen(options.port ?? 0, host, () => {
|
|
123
|
+
server.off("error", reject);
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
const address = server.address();
|
|
128
|
+
if (!address || typeof address === "string") {
|
|
129
|
+
throw new Error("Unable to determine AI Subs dashboard port");
|
|
130
|
+
}
|
|
131
|
+
const url = `${origin}:${address.port}`;
|
|
132
|
+
return {
|
|
133
|
+
server,
|
|
134
|
+
apiKey,
|
|
135
|
+
url,
|
|
136
|
+
bootstrapUrl: `${url}/bootstrap?token=${encodeURIComponent(bootstrapToken)}`,
|
|
137
|
+
close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
|
|
138
|
+
};
|
|
139
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
|
+
import type { SubscriptionAuth } from "./auth.js";
|
|
3
|
+
export declare function readJsonBody(request: IncomingMessage): Promise<unknown>;
|
|
4
|
+
export declare function readProxyBody(request: IncomingMessage, maxBytes?: number): Promise<Buffer>;
|
|
5
|
+
export declare function sendJson(response: ServerResponse, status: number, body: unknown, headers?: Record<string, string>): void;
|
|
6
|
+
export declare function routeSegments(pathname: string): string[];
|
|
7
|
+
export declare function handleSubscriptionAuthApi(auth: SubscriptionAuth, request: IncomingMessage, response: ServerResponse, url: URL, maxProxyBodyBytes?: number): Promise<boolean>;
|
|
8
|
+
export interface SubscriptionAuthServerOptions {
|
|
9
|
+
auth: SubscriptionAuth;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
host?: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
/** Maximum buffered proxy request body. Defaults to 10 MiB. */
|
|
14
|
+
maxProxyBodyBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SubscriptionAuthServer {
|
|
17
|
+
server: Server;
|
|
18
|
+
apiKey: string;
|
|
19
|
+
url: string;
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createSubscriptionAuthServer(options: SubscriptionAuthServerOptions): Promise<SubscriptionAuthServer>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { errorMessage, isRecord, stringValue, urlHost } from "./utils.js";
|
|
5
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
6
|
+
const MAX_PROXY_BODY_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
function proxyBodyLimit(value = MAX_PROXY_BODY_BYTES) {
|
|
8
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
9
|
+
throw new Error("maxProxyBodyBytes must be a positive safe integer");
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
async function readBody(request, maxBytes) {
|
|
14
|
+
const contentLength = Number(request.headers["content-length"]);
|
|
15
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes) {
|
|
16
|
+
throw new Error("Request body is too large");
|
|
17
|
+
}
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let size = 0;
|
|
20
|
+
for await (const chunk of request) {
|
|
21
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
22
|
+
size += buffer.length;
|
|
23
|
+
if (size > maxBytes)
|
|
24
|
+
throw new Error("Request body is too large");
|
|
25
|
+
chunks.push(buffer);
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks);
|
|
28
|
+
}
|
|
29
|
+
export async function readJsonBody(request) {
|
|
30
|
+
const body = await readBody(request, MAX_BODY_BYTES);
|
|
31
|
+
if (body.length === 0)
|
|
32
|
+
return {};
|
|
33
|
+
return JSON.parse(body.toString("utf8"));
|
|
34
|
+
}
|
|
35
|
+
function upstreamHeaders(upstream) {
|
|
36
|
+
const headers = { "cache-control": "no-store" };
|
|
37
|
+
upstream.headers.forEach((value, name) => {
|
|
38
|
+
if (![
|
|
39
|
+
"cache-control",
|
|
40
|
+
"connection",
|
|
41
|
+
"content-encoding",
|
|
42
|
+
"content-length",
|
|
43
|
+
"keep-alive",
|
|
44
|
+
"set-cookie",
|
|
45
|
+
"transfer-encoding",
|
|
46
|
+
].includes(name)) {
|
|
47
|
+
headers[name] = value;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return headers;
|
|
51
|
+
}
|
|
52
|
+
async function sendUpstream(response, upstream) {
|
|
53
|
+
response.writeHead(upstream.status, upstreamHeaders(upstream));
|
|
54
|
+
if (upstream.body)
|
|
55
|
+
await pipeline(Readable.fromWeb(upstream.body), response);
|
|
56
|
+
else
|
|
57
|
+
response.end();
|
|
58
|
+
}
|
|
59
|
+
export async function readProxyBody(request, maxBytes = MAX_PROXY_BODY_BYTES) {
|
|
60
|
+
return readBody(request, proxyBodyLimit(maxBytes));
|
|
61
|
+
}
|
|
62
|
+
export function sendJson(response, status, body, headers = {}) {
|
|
63
|
+
response.writeHead(status, {
|
|
64
|
+
"content-type": "application/json; charset=utf-8",
|
|
65
|
+
"cache-control": "no-store",
|
|
66
|
+
...headers,
|
|
67
|
+
});
|
|
68
|
+
response.end(JSON.stringify(body));
|
|
69
|
+
}
|
|
70
|
+
export function routeSegments(pathname) {
|
|
71
|
+
return pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
72
|
+
}
|
|
73
|
+
export async function handleSubscriptionAuthApi(auth, request, response, url, maxProxyBodyBytes = MAX_PROXY_BODY_BYTES) {
|
|
74
|
+
const parts = routeSegments(url.pathname);
|
|
75
|
+
if (parts[0] === "aisubs" && parts[1] && parts[2] && request.method !== "OPTIONS") {
|
|
76
|
+
const headers = new Headers();
|
|
77
|
+
const privateHeaders = new Set([
|
|
78
|
+
"authorization",
|
|
79
|
+
"connection",
|
|
80
|
+
"content-length",
|
|
81
|
+
"cookie",
|
|
82
|
+
"host",
|
|
83
|
+
"origin",
|
|
84
|
+
"proxy-authenticate",
|
|
85
|
+
"proxy-authorization",
|
|
86
|
+
"referer",
|
|
87
|
+
"te",
|
|
88
|
+
"trailer",
|
|
89
|
+
"upgrade",
|
|
90
|
+
"x-api-key",
|
|
91
|
+
]);
|
|
92
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
93
|
+
if (value != null && !privateHeaders.has(name)) {
|
|
94
|
+
headers.set(name, Array.isArray(value) ? value.join(", ") : value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const body = await readProxyBody(request, maxProxyBodyBytes);
|
|
98
|
+
const path = parts.slice(3).map(encodeURIComponent).join("/") + url.search;
|
|
99
|
+
const upstream = await auth.proxy(parts[1], parts[2], path, {
|
|
100
|
+
method: request.method,
|
|
101
|
+
headers,
|
|
102
|
+
body: body.length ? body : undefined,
|
|
103
|
+
});
|
|
104
|
+
await sendUpstream(response, upstream);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (request.method === "GET" && parts.join("/") === "v1/providers") {
|
|
108
|
+
sendJson(response, 200, { providers: auth.listProviders() });
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (request.method === "GET" && parts.join("/") === "v1/auth") {
|
|
112
|
+
sendJson(response, 200, { sessions: await auth.statuses() });
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (parts[0] === "v1" && parts[1] === "auth" && parts[2]) {
|
|
116
|
+
const provider = parts[2];
|
|
117
|
+
if (request.method === "GET" && parts.length === 3) {
|
|
118
|
+
sendJson(response, 200, await auth.status(provider, {
|
|
119
|
+
account: url.searchParams.get("account") ?? undefined,
|
|
120
|
+
validate: url.searchParams.get("validate") === "true",
|
|
121
|
+
}));
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (request.method === "GET" && parts[3] === "accounts") {
|
|
125
|
+
sendJson(response, 200, { accounts: await auth.listAccounts(provider) });
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (request.method === "GET" && parts[3] === "details") {
|
|
129
|
+
sendJson(response, 200, await auth.credentialSummary(provider, url.searchParams.get("account") ?? undefined));
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (request.method === "POST" && parts[3] === "login") {
|
|
133
|
+
const body = await readJsonBody(request);
|
|
134
|
+
const attempt = await auth.signIn(provider, isRecord(body) ? body : undefined);
|
|
135
|
+
sendJson(response, 202, {
|
|
136
|
+
id: attempt.id,
|
|
137
|
+
provider: attempt.provider,
|
|
138
|
+
accountKey: attempt.accountKey,
|
|
139
|
+
state: attempt.state,
|
|
140
|
+
prompt: attempt.prompt,
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (request.method === "DELETE" && parts.length === 3) {
|
|
145
|
+
const accountKey = url.searchParams.get("account") ?? "default";
|
|
146
|
+
await auth.signOut(provider, accountKey);
|
|
147
|
+
sendJson(response, 200, { provider, accountKey, authenticated: false });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (request.method === "GET" && parts[0] === "v1" && parts[1] === "logins" && parts[2]) {
|
|
152
|
+
const attempt = auth.getLoginAttempt(parts[2]);
|
|
153
|
+
if (attempt)
|
|
154
|
+
sendJson(response, 200, attempt);
|
|
155
|
+
else
|
|
156
|
+
sendJson(response, 404, { error: "Login not found" });
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (request.method === "DELETE" && parts[0] === "v1" && parts[1] === "logins" && parts[2]) {
|
|
160
|
+
if (auth.cancelLoginAttempt(parts[2]))
|
|
161
|
+
sendJson(response, 200, { cancelled: true });
|
|
162
|
+
else
|
|
163
|
+
sendJson(response, 404, { error: "Pending login not found" });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (request.method === "POST" && parts[0] === "v1" && parts[1] === "fetch" && parts[2]) {
|
|
167
|
+
const body = await readJsonBody(request);
|
|
168
|
+
if (!isRecord(body))
|
|
169
|
+
throw new Error("Fetch body requires an absolute url");
|
|
170
|
+
const targetUrl = stringValue(body.url);
|
|
171
|
+
if (!targetUrl)
|
|
172
|
+
throw new Error("Fetch body requires an absolute url");
|
|
173
|
+
const headers = new Headers();
|
|
174
|
+
if (isRecord(body.headers)) {
|
|
175
|
+
for (const [name, value] of Object.entries(body.headers)) {
|
|
176
|
+
if (typeof value === "string")
|
|
177
|
+
headers.set(name, value);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let requestBody;
|
|
181
|
+
if (typeof body.body === "string")
|
|
182
|
+
requestBody = body.body;
|
|
183
|
+
else if (body.body != null) {
|
|
184
|
+
requestBody = JSON.stringify(body.body);
|
|
185
|
+
if (!headers.has("content-type"))
|
|
186
|
+
headers.set("content-type", "application/json");
|
|
187
|
+
}
|
|
188
|
+
const upstream = await auth.fetch(parts[2], targetUrl, {
|
|
189
|
+
method: stringValue(body.method) ?? (requestBody ? "POST" : "GET"),
|
|
190
|
+
headers,
|
|
191
|
+
body: requestBody,
|
|
192
|
+
}, stringValue(body.account));
|
|
193
|
+
await sendUpstream(response, upstream);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (request.method === "GET" && parts[0] === "v1" && parts[1] === "usage" && parts[2]) {
|
|
197
|
+
const usage = await auth.getUsage(parts[2], url.searchParams.get("account") ?? undefined);
|
|
198
|
+
if (usage)
|
|
199
|
+
sendJson(response, 200, usage);
|
|
200
|
+
else
|
|
201
|
+
sendJson(response, 404, { error: "Usage is not supported by this provider" });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (request.method === "GET" && parts[0] === "v1" && parts[1] === "models" && parts[2]) {
|
|
205
|
+
const models = await auth.getModels(parts[2], url.searchParams.get("account") ?? undefined);
|
|
206
|
+
if (models)
|
|
207
|
+
sendJson(response, 200, models);
|
|
208
|
+
else
|
|
209
|
+
sendJson(response, 404, { error: "Models are not supported by this provider" });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
export async function createSubscriptionAuthServer(options) {
|
|
215
|
+
if (!options.apiKey)
|
|
216
|
+
throw new Error("A non-empty API key is required");
|
|
217
|
+
const maxProxyBodyBytes = proxyBodyLimit(options.maxProxyBodyBytes);
|
|
218
|
+
const host = options.host ?? "127.0.0.1";
|
|
219
|
+
if (host !== "127.0.0.1" && host !== "::1" && host !== "localhost") {
|
|
220
|
+
throw new Error("The built-in auth server may only bind to localhost");
|
|
221
|
+
}
|
|
222
|
+
const origin = `http://${urlHost(host)}`;
|
|
223
|
+
const server = createServer(async (request, response) => {
|
|
224
|
+
try {
|
|
225
|
+
const url = new URL(request.url ?? "/", origin);
|
|
226
|
+
if (url.pathname === "/health")
|
|
227
|
+
return sendJson(response, 200, { ok: true });
|
|
228
|
+
const headerKey = Array.isArray(request.headers["x-api-key"])
|
|
229
|
+
? request.headers["x-api-key"][0]
|
|
230
|
+
: request.headers["x-api-key"];
|
|
231
|
+
if (request.headers.authorization !== `Bearer ${options.apiKey}` &&
|
|
232
|
+
headerKey !== options.apiKey) {
|
|
233
|
+
return sendJson(response, 401, { error: "Unauthorized" });
|
|
234
|
+
}
|
|
235
|
+
if (!(await handleSubscriptionAuthApi(options.auth, request, response, url, maxProxyBodyBytes))) {
|
|
236
|
+
sendJson(response, 404, { error: "Not found" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (response.headersSent)
|
|
241
|
+
response.destroy();
|
|
242
|
+
else
|
|
243
|
+
sendJson(response, 400, { error: errorMessage(error) });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
await new Promise((resolve, reject) => {
|
|
247
|
+
server.once("error", reject);
|
|
248
|
+
server.listen(options.port ?? 0, host, () => {
|
|
249
|
+
server.off("error", reject);
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
const address = server.address();
|
|
254
|
+
if (!address || typeof address === "string") {
|
|
255
|
+
throw new Error("Unable to determine auth server port");
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
server,
|
|
259
|
+
apiKey: options.apiKey,
|
|
260
|
+
url: `${origin}:${address.port}`,
|
|
261
|
+
close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
|
|
262
|
+
};
|
|
263
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createSubscriptionAuth, DEFAULT_ACCOUNT, SubscriptionAuth, type SubscriptionAccount, type SubscriptionAuthOptions, } from "./auth.js";
|
|
2
|
+
export { defaultAiSubsDataDir, FileCredentialStore, MemoryCredentialStore } from "./store.js";
|
|
3
|
+
export { chatGptProvider, type ChatGptProviderOptions } from "./providers/chatgpt.js";
|
|
4
|
+
export { claudeProvider, type ClaudeProviderOptions } from "./providers/claude.js";
|
|
5
|
+
export { copilotProvider, type CopilotProviderOptions } from "./providers/copilot.js";
|
|
6
|
+
export { grokProvider, type GrokProviderOptions } from "./providers/grok.js";
|
|
7
|
+
export { openCodeGoProvider, openCodeZenProvider } from "./providers/opencode.js";
|
|
8
|
+
export { parseChatGptUsage, parseCopilotUsage, parseGrokUsage } from "./usage.js";
|
|
9
|
+
export type { CredentialStore, CredentialSummary, BrowserLoginPrompt, DeviceLoginPrompt, ImmediateLoginPrompt, LoginAttempt, LoginMode, LoginPrompt, LoginState, OAuthCredential, ProviderAdapter, ProviderId, ProviderLogin, ProviderLoginField, ProviderModel, ProviderModels, ProviderSummary, ProviderUsage, ProviderUsageContext, ProviderUsageData, Session, SubscriptionAccountDetails, UsageFact, UsageMeter, UsageResetCredit, UsageResetCredits, } from "./types.js";
|