diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 1d7910fcbc..1801202e9d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2188,6 +2188,7 @@ dependencies = [ "codex-utils-cargo-bin", "codex-utils-cli", "codex-utils-path", + "codex-web-server", "codex-windows-sandbox", "libc", "owo-colors", @@ -3936,6 +3937,24 @@ dependencies = [ "v8", ] +[[package]] +name = "codex-web-server" +version = "0.0.0" +dependencies = [ + "anyhow", + "axum", + "clap", + "codex-utils-pty", + "futures", + "include_dir", + "pretty_assertions", + "tokio", + "tokio-tungstenite", + "tracing", + "url", + "webbrowser", +] + [[package]] name = "codex-windows-sandbox" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 44eade38db..e4821a6441 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -104,6 +104,7 @@ members = [ "thread-store", "uds", "codex-experimental-api-macros", + "web-server", "plugin", "model-provider", ] @@ -223,6 +224,7 @@ codex-utils-stream-parser = { path = "utils/stream-parser" } codex-utils-string = { path = "utils/string" } codex-utils-template = { path = "utils/template" } codex-v8-poc = { path = "v8-poc" } +codex-web-server = { path = "web-server" } codex-windows-sandbox = { path = "windows-sandbox-rs" } core_test_support = { path = "core/tests/common" } mcp_test_support = { path = "mcp-server/tests/common" } diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index cdee241b42..0554e1a95d 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -50,6 +50,7 @@ codex-terminal-detection = { workspace = true } codex-tui = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-path = { workspace = true } +codex-web-server = { workspace = true } libc = { workspace = true } owo-colors = { workspace = true } regex-lite = { workspace = true } diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 7b6e7448d4..a8180bd8c8 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -126,6 +126,9 @@ enum Subcommand { /// [experimental] Run the app server or related tooling. AppServer(AppServerCommand), + /// Serve Codex in a browser-backed terminal. + Web(codex_web_server::WebCommand), + /// Launch the Codex desktop app (opens the app installer if missing). #[cfg(any(target_os = "macos", target_os = "windows"))] App(app_cmd::AppCommand), @@ -888,6 +891,14 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> { } } } + Some(Subcommand::Web(web_cli)) => { + reject_remote_mode_for_subcommand( + root_remote.as_deref(), + root_remote_auth_token_env.as_deref(), + "web", + )?; + codex_web_server::run(web_cli, root_config_overrides.raw_overrides.clone()).await?; + } #[cfg(any(target_os = "macos", target_os = "windows"))] Some(Subcommand::App(app_cli)) => { reject_remote_mode_for_subcommand( @@ -2449,6 +2460,30 @@ mod tests { ); } + #[test] + fn web_command_parses_forwarded_args() { + let cli = MultitoolCli::try_parse_from([ + "codex", + "web", + "--listen", + "127.0.0.1:4321", + "--", + "--model", + "gpt-test", + ]) + .expect("parse"); + let Some(Subcommand::Web(web)) = cli.subcommand else { + panic!("expected web subcommand"); + }; + assert_eq!( + web.listen, + "127.0.0.1:4321" + .parse::() + .expect("socket address should parse") + ); + assert_eq!(web.codex_args, vec!["--model", "gpt-test"]); + } + #[test] fn reject_remote_auth_token_env_for_app_server_proxy() { let subcommand = AppServerSubcommand::Proxy(AppServerProxyCommand { socket_path: None }); diff --git a/codex-rs/web-server/BUILD.bazel b/codex-rs/web-server/BUILD.bazel new file mode 100644 index 0000000000..a6dec2dfdc --- /dev/null +++ b/codex-rs/web-server/BUILD.bazel @@ -0,0 +1,7 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "web-server", + crate_name = "codex_web_server", + compile_data = glob(["assets/**"]), +) diff --git a/codex-rs/web-server/Cargo.toml b/codex-rs/web-server/Cargo.toml new file mode 100644 index 0000000000..6c63222956 --- /dev/null +++ b/codex-rs/web-server/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "codex-web-server" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +name = "codex_web_server" +path = "src/lib.rs" + +[lints] +workspace = true + +[dependencies] +anyhow = { workspace = true } +axum = { workspace = true, default-features = false, features = [ + "http1", + "tokio", + "ws", +] } +clap = { workspace = true, features = ["derive"] } +codex-utils-pty = { workspace = true } +futures = { workspace = true } +include_dir = { workspace = true } +tokio = { workspace = true, features = [ + "macros", + "net", + "rt-multi-thread", +] } +tracing = { workspace = true, features = ["log"] } +url = { workspace = true } +webbrowser = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } +tokio-tungstenite = { workspace = true } diff --git a/codex-rs/web-server/assets/assets/ghostty-vt-BG4Ub6dk.wasm b/codex-rs/web-server/assets/assets/ghostty-vt-BG4Ub6dk.wasm new file mode 100644 index 0000000000..bac589b1e7 Binary files /dev/null and b/codex-rs/web-server/assets/assets/ghostty-vt-BG4Ub6dk.wasm differ diff --git a/codex-rs/web-server/assets/assets/index-DZyd-Z17.css b/codex-rs/web-server/assets/assets/index-DZyd-Z17.css new file mode 100644 index 0000000000..1bcd2b9fb5 --- /dev/null +++ b/codex-rs/web-server/assets/assets/index-DZyd-Z17.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ +@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-border-style:solid;--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}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, 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-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-neutral-100:oklch(97% 0 0);--color-neutral-950:oklch(14.5% 0 0);--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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,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;-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{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-none{pointer-events:none}.fixed{position:fixed}.start{inset-inline-start:var(--spacing)}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-3{left:calc(var(--spacing) * 3)}.z-10{z-index:10}.hidden{display:none}.h-dvh{height:100dvh}.h-full{height:100%}.min-h-0{min-height:calc(var(--spacing) * 0)}.max-w-\[calc\(100vw-1\.5rem\)\]{max-width:calc(100vw - 1.5rem)}.resize{resize:both}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-red-500\/40{border-color:#fb2c3666}@supports (color:color-mix(in lab, red, red)){.border-red-500\/40{border-color:color-mix(in oklab, var(--color-red-500) 40%, transparent)}}.bg-neutral-950{background-color:var(--color-neutral-950)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-neutral-100{color:var(--color-neutral-100)}.text-red-200{color:var(--color-red-200)}.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)}}.wterm{--term-fg:#d4d4d4;--term-bg:#1e1e1e;--term-cursor:#aeafad;--term-color-0:#1e1e1e;--term-color-1:#f44747;--term-color-2:#6a9955;--term-color-3:#d7ba7d;--term-color-4:#569cd6;--term-color-5:#c586c0;--term-color-6:#4ec9b0;--term-color-7:#d4d4d4;--term-color-8:gray;--term-color-9:#f44747;--term-color-10:#6a9955;--term-color-11:#d7ba7d;--term-color-12:#569cd6;--term-color-13:#c586c0;--term-color-14:#4ec9b0;--term-color-15:#fff;--term-font-family:"Menlo", "Consolas", "DejaVu Sans Mono", "Courier New", monospace;--term-font-size:14px;--term-line-height:1.2;--term-row-height:17px;background:var(--term-bg);color:var(--term-fg);font-family:var(--term-font-family);font-size:var(--term-font-size);line-height:var(--term-line-height);border-radius:8px;outline:none;padding:12px;position:relative;overflow:hidden;box-shadow:0 8px 32px #0006}.wterm:focus,.wterm:focus-visible{outline:none}.term-grid{white-space:pre;contain:layout paint style;will-change:contents;display:block}.term-row{height:var(--term-row-height);line-height:var(--term-row-height);contain:layout style;display:block}.term-row>span{height:var(--term-row-height);vertical-align:top;display:inline-block}.term-block{width:1ch;overflow:hidden}.term-cursor{outline:1px solid var(--term-cursor);outline-offset:-1px}.wterm.focused .term-cursor{background:var(--term-cursor);color:var(--term-bg);outline:none}.wterm.focused.cursor-blink .term-cursor{animation:1s step-end infinite cursor-blink}@keyframes cursor-blink{0%,to{background:var(--term-cursor);color:var(--term-bg)}50%{color:inherit;background:0 0}}.wterm.has-scrollback{overflow-y:auto}.wterm ::selection{background:#569cd64d}.wterm.theme-solarized-dark{--term-fg:#839496;--term-bg:#002b36;--term-cursor:#93a1a1;--term-color-0:#073642;--term-color-1:#dc322f;--term-color-2:#859900;--term-color-3:#b58900;--term-color-4:#268bd2;--term-color-5:#d33682;--term-color-6:#2aa198;--term-color-7:#eee8d5;--term-color-8:#586e75;--term-color-9:#cb4b16;--term-color-10:#586e75;--term-color-11:#657b83;--term-color-12:#839496;--term-color-13:#6c71c4;--term-color-14:#93a1a1;--term-color-15:#fdf6e3}.wterm.theme-monokai{--term-fg:#f8f8f2;--term-bg:#272822;--term-cursor:#f8f8f0;--term-color-0:#272822;--term-color-1:#f92672;--term-color-2:#a6e22e;--term-color-3:#f4bf75;--term-color-4:#66d9ef;--term-color-5:#ae81ff;--term-color-6:#a1efe4;--term-color-7:#f8f8f2;--term-color-8:#75715e;--term-color-9:#f92672;--term-color-10:#a6e22e;--term-color-11:#f4bf75;--term-color-12:#66d9ef;--term-color-13:#ae81ff;--term-color-14:#a1efe4;--term-color-15:#f9f8f5}.wterm.theme-light{--term-fg:#383a42;--term-bg:#fafafa;--term-cursor:#526eff;--term-color-0:#383a42;--term-color-1:#e45649;--term-color-2:#50a14f;--term-color-3:#c18401;--term-color-4:#4078f2;--term-color-5:#a626a4;--term-color-6:#0184bc;--term-color-7:#fafafa;--term-color-8:#a0a1a7;--term-color-9:#e45649;--term-color-10:#50a14f;--term-color-11:#c18401;--term-color-12:#4078f2;--term-color-13:#a626a4;--term-color-14:#0184bc;--term-color-15:#fff}html,body{min-height:100%;margin:0}body{overflow:hidden}#terminal{padding:max(env(safe-area-inset-top), .5rem) max(env(safe-area-inset-right), .5rem) max(env(safe-area-inset-bottom), .5rem) max(env(safe-area-inset-left), .5rem)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";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:"";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:"";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} diff --git a/codex-rs/web-server/assets/assets/index-DcK3rtbT.js b/codex-rs/web-server/assets/assets/index-DcK3rtbT.js new file mode 100644 index 0000000000..2bc157d8ee --- /dev/null +++ b/codex-rs/web-server/assets/assets/index-DcK3rtbT.js @@ -0,0 +1 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`AGFzbQEAAAABMAlgAAF/YAAAYAF/AX9gAX8AYAJ/fwBgBH9/f38AYAN/f38AYAJ/fwF/YAN/f38BfwMwLwAAAAAAAQAAAgICAAAAAAAAAAAAAAAAAQAAAwMEBAUFBQYHAQYBAQQIBAQDAAQEBAUBcAEBAQUDAQBYBgkBfwFBgIDAAAsH7wMfBm1lbW9yeQIACmdldE1heENvbHMAAAtnZXRDZWxsU2l6ZQABDmdldERlYnVnTG9nTWF4AAIUZ2V0RGVidWdMb2dFbnRyeVNpemUAARBnZXREZWJ1Z0xvZ0NvdW50AAMOZ2V0RGVidWdMb2dQdHIABA1jbGVhclJlc3BvbnNlAAUOZ2V0UmVzcG9uc2VMZW4ABg5nZXRSZXNwb25zZVB0cgAHFGdldFNjcm9sbGJhY2tMaW5lTGVuAAgRZ2V0U2Nyb2xsYmFja0xpbmUAChJnZXRTY3JvbGxiYWNrQ291bnQACw9nZXRUaXRsZUNoYW5nZWQADAtnZXRUaXRsZUxlbgANC2dldFRpdGxlUHRyAA4RZ2V0VXNpbmdBbHRTY3JlZW4ADxFnZXRCcmFja2V0ZWRQYXN0ZQAQEGdldEN1cnNvcktleXNBcHAAEQdnZXRSb3dzABIHZ2V0Q29scwATEGdldEN1cnNvclZpc2libGUAFAxnZXRDdXJzb3JDb2wAFQxnZXRDdXJzb3JSb3cAFgpjbGVhckRpcnR5ABcLZ2V0RGlydHlQdHIAGApnZXRHcmlkUHRyABkKd3JpdGVCeXRlcwAaDmdldFdyaXRlQnVmZmVyACwOcmVzaXplVGVybWluYWwALQRpbml0AC4K+V8vBQBBgAILBABBDAsEAEEgCwsAQQAoAuSEwIAACwgAQfSG8IAACw0AQQBBADoA2IzwgAALCwBBAC0A2IzwgAALCABBmIzwgAALGgACQCAAEImAgIAAIgANAEEADwsgAC8BgBgLXAECf0EAIQECQCAAQQAoAvyFrIIAIgJPDQACQAJAIAJB6AdPDQAgAiAAQX9zaiEADAELQQAoAoCGrIIAIABrQecHakHoB3AhAAsgAEGEGGxB3KbwgABqIQELIAELFQAgABCJgICAACIAQdyO8IAAIAAbCwsAQQAoAvyFrIIACygBAX9BACEAAkBBAC0Al4zwgABFDQBBAEEAOgCXjPCAAEEBIQALIAALCwBBAC8B/InwgAALCABBlorwgAALCwBBAC0AlYrwgAALCwBBAC0AkIrwgAALCwBBAC0AkYrwgAALCwBBAC8B7obwgAALCwBBAC8B7IbwgAALCwBBAC0A24zwgAALCwBBAC8B8obwgAALCwBBAC8B8IbwgAALNwECf0EALwHqhPCAACEAQQAhAQJAA0AgACABRg0BIAFB7ITwgABqQQA6AAAgAUEBaiEBDAALCwsIAEHshPCAAAsIAEHohMCAAAvVOwMFfwF+Bn8jgICAgABBwABrIgEkgICAgAAgAEGAwAAgAEGAwABJGyECQZyAwIAAQQJqIQNBACEAA0ACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgACACRg0AAkACQAJAAkACQAJAAkACQAJAAkACQCAALQCEhqyCACIEQWhqDgQCAQIAAQtBAC0AwoDAgAAhBEEAQQI6AMKAwIAAIARBB3FBB0YNA0EAQQA7AdiAwIAADCcLAkACQAJAAkACQAJAQQAtAMKAwIAAQQdxDggLAAECAwQFBwsLIATAQb9/Sg0JQQAtAN+EwIAAQQAtAN6EwIAAa0EHcUHahMCAAGogBDoAAEEAQQAtAN6EwIAAQX9qQQdxIgQ6AN6EwIAAIAQNK0H9/wMhBAJAAkACQAJAQQAtAN+EwIAAQQdxQX5qDgMAAQIDC0EALQDahMCAAEEfcUEGdEEALQDbhMCAAEE/cXIhBAwCC0EALQDbhMCAAEE/cUEGdEEALQDahMCAAEEPcUEMdHJBAC0A3ITAgABBP3FyIQQMAQtBAC0A24TAgABBP3FBDHRBAC0A2oTAgABBEnRyQQAtANyEwIAAQT9xQQZ0ckEALQDdhMCAAEE/cXIhBAtBACAEOwGcgMCAACADIARBgID8AHFBEHY6AABBAEEAOgDCgMCAAAwLCwJAAkACQCAEQaV/ag4DAAIBAgtBAEEEOgDCgMCAAEEAQQA7AdiAwIAAQQBBADoAxYDAgABBAEEAOgDEgMCAAEHGgMCAACEFQQghBANAIARBKEYNLSAEQZiAwIAAakEAOwEAIAVBADoAACAEQQJqIQQgBUEBaiEFDAALC0EAQQc6AMKAwIAAQQBBADsBwIDAgAAMKwsCQCAEQfABcUEgRw0AAkBBAC0A2IDAgAAiBUEBSw0AIAUgBDoA1oDAgABBAEEALQDYgMCAAEEBajoA2IDAgAALQQBBAzoAwoDAgAAMKwsgBEFQakH/AXFBzwBJDQ8gBEEgSQ0jDAQLAkAgBEHwAXFBIEcNAEEALQDYgMCAACIFQQFLDSogBSAEOgDWgMCAAEEAQQAtANiAwIAAQQFqOgDYgMCAAAwqCyAEQVBqQf8BcUHPAEkNDiAEQSBJDSIMAwsCQAJAAkACQAJAAkAgBEFQakH/AXEiBUEJSw0AQQAtAMWAwIAADS5BAC0AxIDAgAAiBA0BQQAhBEEAQQE6AMSAwIAADAILIARBRmoOBgICBAQsLAMLIARBf2pB/wFxIQQLIARBAXQiBEF/IAQvAaCAwIAAQRB0rUIKfiIGpyAGQiCIpxtBEHYgBWoiBEH//wMgBEH//wNJGzsBoIDAgAAMKwtBAC0AxIDAgAAiBUEPSw0qQQAgBUEBIAVBAUsbIgVBAWo6AMSAwIAAIARBOkcNKiAFQQE6AMaAwIAADCoLIARBIUYNKAsCQCAEQfABcUEgRw0AAkBBAC0A2IDAgAAiBUEBSw0AIAUgBDoA1oDAgABBAEEALQDYgMCAAEEBajoA2IDAgAALQQBBBToAwoDAgAAMKQsgBEFAakH/AXFBP0kNCSAEQSBJDSEMJgsCQCAEQfABcUEgRw0AQQAtANiAwIAAIgVBAUsNKCAFIAQ6ANaAwIAAQQBBAC0A2IDAgABBAWo6ANiAwIAADCgLIARBQGpB/wFxQT9JDQggBEEgTw0lDCALIARBQGpB/wFxQT5LDSYLQQBBADoAwoDAgAAMJQsgBEEHRw0BQQBBADoAwoDAgAALQQAvAcCAwIAAIgRBAkkNI0EALQDagMCAAEFQag4DByMHIwsgBEFgakH/AXFB3gBLDSJBAC8BwIDAgAAiBUH/A0sNIiAFIAQ6ANqAwIAAQQAgBUEBajsBwIDAgAAMIgtBAEEAOgDCgMCAAAsgBEEgSQ0ZAkAgBEH/AEkNAAJAIARB/wBHDQBBAEH/ADoAw4DAgAAMHAsCQCAEQeABcUHAAUcNAEEAQQI6AN+EwIAAQQAgBDoA2oTAgABBAEEBOgDehMCAAEEAQQE6AMKAwIAADCILAkAgBEHwAXFB4AFHDQBBAEEDOgDfhMCAAEEAIAQ6ANqEwIAAQQBBAjoA3oTAgABBAEEBOgDCgMCAAAwiCyAEQfgBcUHwAUcNIUEAQQQ6AN+EwIAAQQAgBDoA2oTAgABBAEEDOgDehMCAAEEAQQE6AMKAwIAADCELQQAgBDsBnIDAgAAgA0EAOgAAC0EAKAKcgMCAACEEQQAtANmM8IAADQEMFwtBACAEOgDDgMCAAEEAQQA6AMKAwIAAAkBBAC0A2YDAgAAiBUE/Rw0AAkACQAJAIARBmH9qDgUAAgICAQILQQEQm4CAgAAMIQtBABCbgICAAAwgCyAEQT8QnICAgAAMHwsCQCAEQfAARw0AIAVBIUcNAEEAQQE6AJKK8IAAQQBBAToA24zwgABBAEGAgoAINgH+ifCAAEEAQQAvAe6G8IAAOwGGivCAAEEAQQA6AI6K8IAAQQBBADoAkYrwgABBAEEAOgCQivCAAEEAQQA7AYSK8IAAQQBBADoAk4rwgAAMHwsCQCAFQT5HDQAgBEE+EJyAgIAADB8LAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIARBQGoONiQAAQIDBAUGIiEHCAkKISELISEMDSEhIQ4hISEhISEhIw8hIRARIhIhISEhIRMmISEhJRkzGiELQQBBAEEALwHwhvCAACIEQQAvAaCAwIAAIgVBASAFQQFLG0EBQQAtAMSAwIAAG2siBSAFIARLGzsB8IbwgABBAEEAOgDZjPCAAAwyC0EAQQAvAfCG8IAAQQAvAaCAwIAAIgRBASAEQQFLG0EBQQAtAMSAwIAAG2pB//8DcSIEQQAvAe6G8IAAQX9qQf//A3EiBSAEIAVJGzsB8IbwgABBAEEAOgDZjPCAAAwxC0EAQQAvAfKG8IAAQQAvAaCAwIAAIgRBASAEQQFLG0EBQQAtAMSAwIAAG2pB//8DcSIEQQAvAeyG8IAAQX9qQf//A3EiBSAEIAVJGzsB8obwgABBAEEAOgDZjPCAAAwwC0EAQQBBAC8B8obwgAAiBEEALwGggMCAACIFQQEgBUEBSxtBAUEALQDEgMCAABtrIgUgBSAESxs7AfKG8IAAQQBBADoA2YzwgAAMLwtBAEEALwHwhvCAAEEALwGggMCAACIEQQEgBEEBSxtBAUEALQDEgMCAABtqQf//A3EiBEEALwHuhvCAAEF/akH//wNxIgUgBCAFSRs7AfCG8IAAQQBBADoA2YzwgABBAEEAOwHyhvCAAAwuC0EAQQBBAC8B8IbwgAAiBEEALwGggMCAACIFQQEgBUEBSxtBAUEALQDEgMCAABtrIgUgBSAESxs7AfCG8IAAQQBBADoA2YzwgABBAEEAOwHyhvCAAAwtC0EAQQBBAC8BoIDAgAAiBEF/aiIFIAUgBEsbQQBBAC0AxIDAgAAbIgRBAC8B7IbwgAAiBUF/aiAEIAVJGzsB8obwgABBAEEAOgDZjPCAAAwsCwJAQQAtAMSAwIAADQAgAUGAAjsBBCABQSA2AgAgAUEANgIIIAFBAC8BgIrwgAA7AQYMIwtBAC8BoIDAgAAhBSABQQA2AgggAUEALwGAivCAADsBBiABQYACOwEEIAFBIDYCAAJAAkAgBQ4EJAABAS0LQQAhBAJAA0AgBEH//wNxQQAvAfCG8IAAIgVPDQEgBCABEJ2AgIAAIARBAWohBAwACwsgBUEAQQAvAfKG8IAAQQFqIAEQnoCAgAAMLAtBACEEAkADQCAEQf//A3FBAC8B7obwgABPDQEgBCABEJ2AgIAAIARBAWohBAwACwsgBUEDRw0rQQAoAuCEwIAAIgRFDSsgBEIANwKg37sBDCsLQQAtAMSAwIAADQsgAUGAAjsBBCABQSA2AgAgAUEANgIIIAFBAC8BgIrwgAA7AQYMIAtBAC8B8IbwgAAiBEEALwGEivCAAEkNKSAEQQAvAYaK8IAAIgVPDSlBAC8BoIDAgAAhB0EALQDEgMCAACEIIAFBgAI7AQQgAUEgNgIAIAFBADYCCCABQQAvAYCK8IAAOwEGIAQgBSAHQQEgB0EBSxtBASAIGyABEJ+AgIAADCkLQQAvAfCG8IAAIgRBAC8BhIrwgABJDSggBEEALwGGivCAACIFTw0oQQAvAaCAwIAAIQdBAC0AxIDAgAAhCCABQYACOwEEIAFBIDYCACABQQA2AgggAUEALwGAivCAADsBBiAEIAUgB0EBIAdBAUsbQQEgCBsgARCggICAAAwoC0EALwHyhvCAACIEQQxsIghB8ITAgABqIQVBAC8BoIDAgAAiB0EBIAdBAUsbQQFBAC0AxIDAgAAbIglBDGxB6ITAgABqIQpBAC8BgIrwgAAhCwJAA0AgCSAEakEALwHshvCAACIHTw0BIAhBAC8B8IbwgABBgBhsaiIHQeiEwIAAaiAKIAdqIgwpAgA3AgAgB0HwhMCAAGogDEEIaigCADYCACAFQQxqIQUgCEEMaiEIIARBAWohBAwACwsCQANAQQAvAfCG8IAAIQggBCAHQf//A3FPDQEgBSAIQYAYbGoiB0EANgIAIAdBfmogCzsBACAHQXxqQYACOwEAIAdBeGpBIDYCACAFQQxqIQUgBEEBaiEEQQAvAeyG8IAAIQcMAAsLIAhBAToA7ITwgAAMJwtBAC8BoIDAgAAiBEEBIARBAUsbQQFBAC0AxIDAgAAbIQhBAC8BhIrwgAAhBQJAQQAtAJWK8IAADQAgBUH//wNxDQBBACEFQQAoAuCEwIAAIgxFDQBBACEFQQAhBANAIARB//8DcSIHIAhPDQEgB0EALwGGivCAACAFa0H//wNxTw0BIAwgBCAFakH//wNxQYAYbEHohMCAAGpBAC8B7IbwgAAQoYCAgAAgBEEBaiEEQQAvAYSK8IAAIQUMAAsLIAFBgAI7AQQgAUEgNgIAIAFBADYCCCABQQAvAYCK8IAAOwEGIAVBAC8BhorwgAAgCCABEKCAgIAADCYLQQAtAMSAwIAAIQVBAC8BoIDAgAAhBCABQQA2AgggAUEALwGAivCAADsBBiABQYACOwEEIAFBIDYCAEEALwGEivCAAEEALwGGivCAACAEQQEgBEEBSxtBASAFGyABEJ+AgIAADCULQQAtAMSAwIAAIQVBAC8BoIDAgAAhBCABQQA2AgggAUEALwGAivCAADsBBiABQYACOwEEIAFBIDYCAEEALwHwhvCAAEEALwHyhvCAACIHIAcgBEEBIARBAUsbQQEgBRtqQf//A3EiBEEALwHshvCAACIFIAQgBUkbIAEQnoCAgAAMJAtBAEEALwHyhvCAAEEALwGggMCAACIEQQEgBEEBSxtBAUEALQDEgMCAABtqQf//A3EiBEEALwHshvCAAEF/akH//wNxIgUgBCAFSRs7AfKG8IAAQQBBADoA2YzwgAAMIwtBAEEAQQAvAaCAwIAAIgRBf2oiBSAFIARLG0EAQQAtAMSAwIAAGyIEQQAvAe6G8IAAIgVBf2ogBCAFSRs7AfCG8IAAQQBBADoA2YzwgAAMIgtBAEEALwHwhvCAAEEALwGggMCAACIEQQEgBEEBSxtBAUEALQDEgMCAABtqQf//A3EiBEEALwHuhvCAAEF/akH//wNxIgUgBCAFSRs7AfCG8IAAQQBBADoA2YzwgAAMIQsCQAJAQQAtAMSAwIAARQ0AQQAvAaCAwIAADgQAIiIBIgtBAC8B8obwgAAiBEH/AUsNISAEQQA6ANyM8IAADCELQcSMMCEEA0AgBEHEjjBGDSEgBEGYgMCAAGpBADoAACAEQQFqIQQMAAsLQQAhBAJAQQAtAMSAwIAAIgUNAEEAQYCCgAg2Af6J8IAAQQBBADoAk4rwgAAMIAsDQCAEQf8BcSIHIAVB/wFxTw0gAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAHQQF0LwGggMCAACIIDjIAAQIDBAUUBgcIFBQUFBQUFBQUFBQUCQoLDBQNDg8UFBQUFBQUFBARFBQUFBQUFBQSExQLQQBBgIKACDYB/onwgABBAEEAOgCTivCAAAwbC0EAQQAtAJOK8IAAQQFyOgCTivCAAAwaC0EAQQAtAJOK8IAAQQJyOgCTivCAAAwZC0EAQQAtAJOK8IAAQQRyOgCTivCAAAwYCyAEQQFqIghB/wFxIgcgBUH/AXFJDRBBAC0Ak4rwgAAhBQwVC0EAQQAtAJOK8IAAQRByOgCTivCAAAwWC0EAQQAtAJOK8IAAQSByOgCTivCAAAwVC0EAQQAtAJOK8IAAQcAAcjoAk4rwgAAMFAtBAEEALQCTivCAAEGAAXI6AJOK8IAADBMLQQBBAC0Ak4rwgABB/AFxOgCTivCAAAwSC0EAQQAtAJOK8IAAQfsBcToAk4rwgAAMEQtBAEEALQCTivCAAEH3AXE6AJOK8IAADBALQQBBAC0Ak4rwgABB7wFxOgCTivCAAAwPC0EAQQAtAJOK8IAAQd8BcToAk4rwgAAMDgtBAEEALQCTivCAAEG/AXE6AJOK8IAADA0LQQBBAC0Ak4rwgABB/wBxOgCTivCAAAwMCyAEQf6J8IAAEKKAgIAAQf8BcSAEaiEEDAsLQQBBgAI7Af6J8IAADAoLIARBgIrwgAAQooCAgABB/wFxIARqIQQMCQtBAEGAAjsBgIrwgAAMCAsgCEFiaiIMQf//A3FBCEkNBCAIQfj/A3FBKEYNAyAIQaZ/akH//wNxQQhJDQIgCEGcf2pB//8DcUEISQ0BIAVBf2ohBCAFQf8BcUF/aiEIA0AgCCAHRg0IIAdBx4DAgABqIQUgB0EBaiIMIQcgBS0AAEUNBwwACwtBAC0Ak4rwgAAhBSAHLQDGgMCAAEUNBEEAQQhBACAHQQF0LwGggMCAABsgBUH3AXFyOgCTivCAACAIIQQMBgtBACAIQaR/ajsBgIrwgAAMBQtBACAIQa5/ajsB/onwgAAMBAtBACAIQVhqOwGAivCAAAwDC0EAIAw7Af6J8IAADAILQQAgBUEIcjoAk4rwgAAMAQsgDEF/aiEECyAEQQFqIQRBAC0AxIDAgAAhBQwACwtBAC8BoIDAgAAhBCABQQA2AgggAUEALwGAivCAADsBBiABQYACOwEEIAFBIDYCACAEDgMUExIeC0EAQQA7AfKG8IAAEKOAgIAAQQBBADoA2YzwgAAMFQsgAUHAAGokgICAgAAPC0EALQDbgMCAAEE7Rw0bIARBfmoiBEGAAiAEQYACSRshBUEAIQQCQANAIAUgBEYNASAEQZaK8IAAaiAEQdyAwIAAai0AADoAACAEQQFqIQQMAAsLQQBBAToAl4zwgABBACAFOwH8ifCAAAwbC0EAIAQ6AMOAwIAAQQBBADoAwoDAgAACQEEALQDYgMCAAEUNAEEALQDWgMCAAEH/AXFBI0cNAAJAIARBvH9qDgoFBBwcCRwcHBwGAAsCQCAEQUlqDgICAAcLQQAhBQJAA0AgBUH//wNxQQAvAe6G8IAATw0BQQAhBAJAA0AgBEH//wNxQQAvAeyG8IAATw0BIAUgBEGMgMCAABCkgICAACAEQQFqIQQMAAsLIAVBAWohBQwACwtBAEEANgLwhvCAAAwbCwJAIARBvH9qDgoEAxsbCBsbGxsFAAsCQCAEQUlqDgIBAgALIARB4wBGDQYMGgsQpYCAgAAMGQsQpoCAgAAMGAtBAEEAOgDZjPCAAEEAQQA7AfKG8IAACxCjgICAAAwWCwJAQQAvAfCG8IAAIgRBAC8BhIrwgABHDQAgAUGAAjsBBCABQSA2AgAgAUEANgIIIAFBAC8BgIrwgAA7AQYgBEEALwGGivCAAEEBIAEQn4CAgAAMFgsgBEUNFUEAIARBf2o7AfCG8IAADBULIARB4wBHDRQLQQAvAeyG8IAAQQAvAe6G8IAAEKeAgIAADBMLQQAvAfKG8IAAIgRB/wFLDRIgBEEBOgDcjPCAAAwSCyAEQQAQnICAgAAMEQtBAEEAQQAvAaCAwIAAIgRBf2oiBSAFIARLG0EAQQAtAMSAwIAAIgQbIgVBAC8B7obwgAAiB0F/aiAFIAdJGzsB8IbwgABBAEEAQQAvAaKAwIAAIgVBf2oiByAHIAVLG0EAIARBAUsbIgRBAC8B7IbwgAAiBUF/aiAEIAVJGzsB8obwgABBAEEAOgDZjPCAAAwQC0EAQQBBAC8BoIDAgAAiBEF/aiIFIAUgBEsbQQBBAC0AxIDAgAAbIgRBAC8B7IbwgAAiBUF/aiAEIAVJGzsB8obwgABBAEEAOgDZjPCAAAwPC0EALQDEgMCAACEHQQAvAaCAwIAAIQQgAUEANgIIIAFBAC8BgIrwgAA7AQYgAUGAAjsBBCABQSA2AgACQEEALwHyhvCAACIFIARBASAEQQFLG0EBIAcbIgdqQf//A3FBAC8B7IbwgAAiBEkNAEEALwHwhvCAACAFIAQgARCegICAAAwPCwJAA0AgBEF/aiIEQf//A3EgBSAHaiIIQf//A3FJDQFBAC8B8IbwgABBgBhsQeiEwIAAaiIFIAQgB2tB//8DcUEMbGoiCCkCACEGIAUgBEH//wNxQQxsaiIFQQhqIAhBCGooAgA2AgAgBSAGNwIAQQAvAfKG8IAAIQUMAAsLQQAgCEH//wNxIgRBAC8B7IbwgAAiByAEIAdJGyIEIAVB//8DcSIFayIHIAcgBEsbIQQgBUEMbEHohMCAAGohBQJAA0BBAC8B8IbwgAAhByAERQ0BIAUgB0GAGGxqIgcgASkCADcCACAHQQhqIAFBCGooAgA2AgAgBEF/aiEEIAVBDGohBQwACwsgB0EBOgDshPCAAAwOC0EAQQAvAaCAwIAAIgRBf2oiBSAFIARLG0EAQQAtAMSAwIAAIgcbIghBAC8BooDAgAAiBUEALwHuhvCAACIEIAUgBEkbIAQgBRsgBCAHQQFLGyIETw0NQQAgBDsBhorwgABBACAIOwGEivCAAEEAIAhBAEEALQCOivCAABs7AfCG8IAAQQBBADoA2YzwgABBAEEAOwHyhvCAAAwNC0EALQDEgMCAAEUNDEEALwGggMCAAEH//wNxQQZHDQwgAUGbtgE7AABBAC8B8obwgAAhBCABIAFBAkEALwHwhvCAAEEBahCogICAACIFQf8BcWpBOzoAACABIAEgBUEBaiAEQQFqEKiAgIAAIgRB/wFxakHSADoAAAJAQcAARQ0AQZiM8IAAIAFBwAD8CgAAC0EAIARBAWo6ANiM8IAADAwLQQAvAfCG8IAAIAEQnYCAgAAMCwtBAC8B8IbwgABBAEEALwHyhvCAAEEBaiABEJ6AgIAADAoLQQAvAfCG8IAAQQAvAfKG8IAAQQAvAeyG8IAAIAEQnoCAgAAMCQtBAC8B8IbwgABBAC8B8obwgABBAC8B7IbwgAAgARCegICAAEEALwHwhvCAACEEA0AgBEEBaiIEQf//A3FBAC8B7obwgABPDQkgBCABEJ2AgIAADAALCyABQQA6AAsgAUEAOwAJIAFBAC0Ak4rwgAA6AAggAUEAKAH+ifCAADYCBCABIARB////AHE2AgBBAC8B8IbwgABBAC8B8obwgAAgARCkgICAAAJAQQAvAfKG8IAAIgRBAC8B7IbwgABBf2pB//8DcU8NAEEAIARBAWo7AfKG8IAADAgLQQAtAJKK8IAARQ0HQQBBAToA2YzwgAAMBwtBACEFQQAgBDoAw4DAgAAgBEF4ag4GAAECAgIDBgtBAC8B8obwgAAiBEUNBSAEQX9qIQUMAgtBAC8B7IbwgAAiCEEALwHyhvCAACIEQQFqQf//A3EiBSAIIAVLGyEMAkADQCAEQQFqIgUgCE8NASAEQd2M8IAAaiEHIAUhBCAHLQAAQQFHDQALIAUhDAsgDCAIQX9qIAUgCEkbIQUMAQsQo4CAgABBACEFQQAtAI+K8IAARQ0DC0EAIAU7AfKG8IAAQQBBADoA2YzwgAAMAgtBAEEGOgDCgMCAAAwBC0EAIAQ6ANmAwIAACyAAQQFqIQAMAAsLsQIBA39BAC0AxIDAgAAiAUEBIAFBAUsbQQF0IQJBACEBA0ACQAJAAkAgAiABRg0AAkACQAJAAkACQAJAAkACQAJAAkAgAUGggMCAAGovAQAiA0F/ag4HAQwMDAwCAwALAkAgA0Hpd2oOAwYJBwALIANBFEYNAyADQRlGDQQgA0EvRg0FIANB1A9GDQcMCwtBACAAQQFxOgCRivCAAAwKC0EAIABBAXE6AI6K8IAADAkLQQAgAEEBcToAkorwgAAMCAtBACAAQQFxOgCPivCAAAwHC0EAIABBAXE6ANuM8IAADAYLIABBABCpgICAAAwFCyAAQQEQqYCAgAAMBAtBACAAQQFxOgCQivCAAAwDCyAAQQFxDQEQpoCAgAAMAgsPCxClgICAAAsgAUECaiEBDAALC+sBAQJ/I4CAgIAAQRBrIgIkgICAgABBACEDIAJBADoACyACIAE6AAkgAiAAOgAIIAJCADcDACACQQAtAMSAwIAAIgE6AAogAUEEIAFBBEkbQQF0IQECQANAIAEgA0YNASACIANqIANBoIDAgABqLwEAOwEAIANBAmohAwwACwtBAC0A2ozwgABBDGwiA0H8hvCAAGogAkEIaigCADYCACADIAIpAwA3AvSG8IAAQQBBACgC5ITAgABBAWoiA0F/IAMbNgLkhMCAAEEAQQAtANqM8IAAQQFqQR9xOgDajPCAACACQRBqJICAgIAAC3gBAn8CQCAAQf//A3EiAkEALwHqhPCAAE8NACACQYAYbEHohMCAAGohAEEAIQMCQANAIANBAC8B6ITwgABPDQEgACABKQIANwIAIABBCGogAUEIaigCADYCACAAQQxqIQAgA0EBaiEDDAALCyACQQE6AOyE8IAACwujAQEBfwJAIABB//8DcSIEQQAvAeqE8IAATw0AQQAgAkH//wNxIgBBAC8B6ITwgAAiAiAAIAJJGyIAIAFB//8DcSIBayICIAIgAEsbIQIgBEGAGGwgAUEMbGpB6ITAgABqIQACQANAIAJFDQEgACADKQIANwIAIABBCGogA0EIaigCADYCACAAQQxqIQAgAkF/aiECDAALCyAEQQE6AOyE8IAACwvhAQEEfwJAIAJB//8DcUUNACABQf//A3EgAEH//wNxTQ0AIAEgAGsiBCACQf//A3EiAiAEQf//A3EiBCACIARJGyIFa0H//wNxIQZBACECA0ACQCAGIAJHDQAgBSAAakH//wNxIQIDQCAAQf//A3EgAk8NAyAAIAMQnYCAgAAgAEEBaiEADAALCyABIAJBf3NqIgdB//8DcSEEAkBBgBhFDQAgBEGAGGxB6ITAgABqIAcgBWtB//8DcUGAGGxB6ITAgABqQYAY/AoAAAsgBEEBOgDshPCAACACQQFqIQIMAAsLC9gBAQN/AkAgAkH//wNxRQ0AIAFB//8DcSAAQf//A3FNDQAgAkH//wNxIgIgASAAa0H//wNxIgQgAiAESRsiBEGAGGxB6ITAgABqIQUgAUH//wNxIQYgAEH//wNxIgBBgBhsIQIDQAJAIAQgAGogBkkN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`;function t(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e=r?e%r:0;for(let e=0;e>16&255},${e>>8&255},${e&255})`}function f(e){if(e===r)return null;if(e<16)return`var(--term-color-${e})`;if(e<232){let t=e-16;return`rgb(${Math.floor(t/36)*51},${Math.floor(t/6)%6*51},${t%6*51})`}let t=(e-232)*10+8;return`rgb(${t},${t},${t})`}function p(e,t){return t===void 0?f(e):d(t)}function m(e,t){return t===void 0?f(e):d(t)}function h(e,t,n,d,f){let h=e,g=t,_=d,v=f;if(n&c){let e=h;h=g,g=e;let t=_;_=v,v=t,_===void 0&&h===r&&(h=0),v===void 0&&g===r&&(g=7)}let y=p(h,_),b=m(g,v),x=``;y&&(x+=`color:${y};`),b&&(x+=`background:${b};`),n&i&&(x+=`font-weight:bold;`),n&a&&(x+=`opacity:0.5;`),n&o&&(x+=`font-style:italic;`);let S=[];return n&s&&S.push(`underline`),n&u&&S.push(`line-through`),S.length&&(x+=`text-decoration:${S.join(` `)};`),n&l&&(x+=`visibility:hidden;`),x}function g(e){return e.replace(/&/g,`&`).replace(//g,`>`)}function _(e,t,n,i,a){let o=e,s=t,l=i,u=a;return n&c&&([o,s]=[s,o],[l,u]=[u,l],l===void 0&&o===r&&(o=0),u===void 0&&s===r&&(s=7)),{fg:p(o,l)||`var(--term-fg)`,bg:m(s,u)||`var(--term-bg)`}}function v(e,t,n){switch(e){case 9600:return`linear-gradient(${t} 50%,${n} 50%)`;case 9601:return`linear-gradient(${n} 87.5%,${t} 87.5%)`;case 9602:return`linear-gradient(${n} 75%,${t} 75%)`;case 9603:return`linear-gradient(${n} 62.5%,${t} 62.5%)`;case 9604:return`linear-gradient(${n} 50%,${t} 50%)`;case 9605:return`linear-gradient(${n} 37.5%,${t} 37.5%)`;case 9606:return`linear-gradient(${n} 25%,${t} 25%)`;case 9607:return`linear-gradient(${n} 12.5%,${t} 12.5%)`;case 9608:return t;case 9609:return`linear-gradient(to right,${t} 87.5%,${n} 87.5%)`;case 9610:return`linear-gradient(to right,${t} 75%,${n} 75%)`;case 9611:return`linear-gradient(to right,${t} 62.5%,${n} 62.5%)`;case 9612:return`linear-gradient(to right,${t} 50%,${n} 50%)`;case 9613:return`linear-gradient(to right,${t} 37.5%,${n} 37.5%)`;case 9614:return`linear-gradient(to right,${t} 25%,${n} 25%)`;case 9615:return`linear-gradient(to right,${t} 12.5%,${n} 12.5%)`;case 9616:return`linear-gradient(to right,${n} 50%,${t} 50%)`;case 9617:return`color-mix(in srgb,${t} 25%,${n})`;case 9618:return`color-mix(in srgb,${t} 50%,${n})`;case 9619:return`color-mix(in srgb,${t} 75%,${n})`;case 9620:return`linear-gradient(${t} 12.5%,${n} 12.5%)`;case 9621:return`linear-gradient(to right,${n} 87.5%,${t} 87.5%)`;default:{let r={9622:[!1,!1,!0,!1],9623:[!1,!1,!1,!0],9624:[!0,!1,!1,!1],9625:[!0,!1,!0,!0],9626:[!0,!1,!1,!0],9627:[!0,!0,!0,!1],9628:[!0,!0,!1,!0],9629:[!1,!0,!1,!1],9630:[!1,!0,!0,!1],9631:[!1,!0,!0,!0]}[e];if(!r)return t;let[i,a,o,s]=r;if(i&&a&&o&&s)return t;let c=[],l=[`0 0`,`100% 0`,`0 100%`,`100% 100%`];return r.forEach((e,n)=>{e&&c.push(`linear-gradient(${t},${t}) ${l[n]}/50% 50% no-repeat`)}),c.push(n),c.join(`,`)}}}var y=class{constructor(e){this.rows=0,this.cols=0,this.rowEls=[],this.prevCursorRow=-1,this.prevCursorCol=-1,this.prevContainerBg=``,this.prevRowBg=[],this._scrollbackRowEls=[],this._renderedScrollbackCount=0,this.container=e}setup(e,t){this.cols=e,this.rows=t,this.container.innerHTML=``,this.rowEls=[],this.prevRowBg=[],this._scrollbackRowEls=[],this._renderedScrollbackCount=0;let n=document.createDocumentFragment();for(let e=0;e{if(!u)return;let t=g(u);if(i>=d&&i${g(n)}`:`${g(n)}`),s+=l?`${g(r)}`:`${g(r)}`,a&&(s+=l?`${g(a)}`:`${g(a)}`)}else s+=l?`${t}`:`${t}`};for(let e=0;e=9600&&c<=9631){f(e);let t=_(r.fg,r.bg,r.flags,r.fgRgb,r.bgRgb),n=e===i?`term-block term-cursor`:`term-block`,o=v(c,t.fg,t.bg),p=r.flags&a?`opacity:0.5;`:``;s+=``,l=``,u=``,d=e+1}else{let t=o&&c>=32?String.fromCodePoint(c):` `,n=o?h(r.fg,r.bg,r.flags,r.fgRgb,r.bgRgb):``;n===l?u+=t:(f(e),l=n,u=t,d=e)}}f(this.cols),e.innerHTML=s;let p=``;if(n>=this.cols&&this.cols>0){let e=t(this.cols-1),n=e.bg,i=e.bgRgb;e.flags&c&&(n=e.fg,i=e.fgRgb,i===void 0&&n===r&&(n=7)),p=m(n,i)||``}let y=p?`0 1px 0 ${p}`:``;o>=0?p!==(this.prevRowBg[o]??``)&&(e.style.background=p,e.style.boxShadow=y,this.prevRowBg[o]=p):(e.style.background=p,e.style.boxShadow=y)}_buildScrollbackRowEl(e,t){let n=document.createElement(`div`);n.className=`term-row term-scrollback-row`;let r=e.getScrollbackLineLen(t);return this._buildRowContent(n,n=>e.getScrollbackCell(t,n),r,-1,-1),n}syncScrollback(e){let t=e.getScrollbackCount();if(t!==this._renderedScrollbackCount){if(t>this._renderedScrollbackCount){let n=t-this._renderedScrollbackCount,r=this.rowEls[0]??null,i=document.createDocumentFragment();for(let t=n-1;t>=0;t--){let n=this._buildScrollbackRowEl(e,t);i.appendChild(n),this._scrollbackRowEls.push(n)}this.container.insertBefore(i,r)}else{let e=this._renderedScrollbackCount-t;for(let t=0;te.getCell(t,n),this.cols,n,t)}}if(this.prevCursorRow=a.row,this.prevCursorCol=a.col,i||e.isDirtyRow(this.rows-1)){let t=e.getCell(this.rows-1,this.cols-1),n=t.bg,i=t.bgRgb;t.flags&c&&(n=t.fg,i=t.fgRgb,i===void 0&&n===r&&(n=7));let a=m(n,i)||``;a!==this.prevContainerBg&&(this.container.style.background=a,this.prevContainerBg=a)}e.clearDirty()}},b={ArrowUp:`\x1B[A`,ArrowDown:`\x1B[B`,ArrowRight:`\x1B[C`,ArrowLeft:`\x1B[D`,Home:`\x1B[H`,End:`\x1B[F`},x={ArrowUp:`\x1BOA`,ArrowDown:`\x1BOB`,ArrowRight:`\x1BOC`,ArrowLeft:`\x1BOD`,Home:`\x1BOH`,End:`\x1BOF`},S={Enter:`\r`,Backspace:``,Tab:` `,Escape:`\x1B`,Insert:`\x1B[2~`,Delete:`\x1B[3~`,PageUp:`\x1B[5~`,PageDown:`\x1B[6~`,F1:`\x1BOP`,F2:`\x1BOQ`,F3:`\x1BOR`,F4:`\x1BOS`,F5:`\x1B[15~`,F6:`\x1B[17~`,F7:`\x1B[18~`,F8:`\x1B[19~`,F9:`\x1B[20~`,F10:`\x1B[21~`,F11:`\x1B[23~`,F12:`\x1B[24~`},C=class{constructor(e,t,n){this.composing=!1,this.element=e,this.onData=t,this.getBridge=n,this.textarea=document.createElement(`textarea`),this.textarea.setAttribute(`autocapitalize`,`off`),this.textarea.setAttribute(`autocomplete`,`off`),this.textarea.setAttribute(`autocorrect`,`off`),this.textarea.setAttribute(`spellcheck`,`false`),this.textarea.setAttribute(`enterkeyhint`,`send`),this.textarea.setAttribute(`tabindex`,`0`),this.textarea.setAttribute(`aria-hidden`,`true`);let r=this.textarea.style;r.position=`absolute`,r.left=`-9999px`,r.top=`0`,r.width=`1px`,r.height=`1px`,r.opacity=`0`,r.overflow=`hidden`,r.border=`0`,r.padding=`0`,r.margin=`0`,r.outline=`none`,r.resize=`none`,r.pointerEvents=`none`,r.caretColor=`transparent`,r.color=`transparent`,r.background=`transparent`,e.appendChild(this.textarea),this._onKeyDown=this.handleKeyDown.bind(this),this._onPaste=this.handlePaste.bind(this),this._onCompositionStart=this.handleCompositionStart.bind(this),this._onCompositionEnd=this.handleCompositionEnd.bind(this),this._onInput=this.handleInput.bind(this),this._onFocus=()=>this.element.classList.add(`focused`),this._onBlur=()=>this.element.classList.remove(`focused`),this.textarea.addEventListener(`keydown`,this._onKeyDown),this.textarea.addEventListener(`paste`,this._onPaste),this.textarea.addEventListener(`compositionstart`,this._onCompositionStart),this.textarea.addEventListener(`compositionend`,this._onCompositionEnd),this.textarea.addEventListener(`input`,this._onInput),this.textarea.addEventListener(`focus`,this._onFocus),this.textarea.addEventListener(`blur`,this._onBlur)}focus(){this.textarea.focus({preventScroll:!0})}destroy(){this.textarea.removeEventListener(`keydown`,this._onKeyDown),this.textarea.removeEventListener(`paste`,this._onPaste),this.textarea.removeEventListener(`compositionstart`,this._onCompositionStart),this.textarea.removeEventListener(`compositionend`,this._onCompositionEnd),this.textarea.removeEventListener(`input`,this._onInput),this.textarea.removeEventListener(`focus`,this._onFocus),this.textarea.removeEventListener(`blur`,this._onBlur),this.element.classList.remove(`focused`),this.textarea.remove()}handleKeyDown(e){if(this.composing)return;if((e.metaKey||e.ctrlKey)&&e.key===`c`){let e=window.getSelection();if(e&&e.toString().length>0)return}if((e.metaKey||e.ctrlKey)&&e.key===`v`){this.textarea.focus();return}if(e.metaKey&&!e.ctrlKey){if(e.key===`Backspace`)e.preventDefault(),this.onData(``);else if(e.key===`a`){e.preventDefault();let t=window.getSelection();if(t){let e=document.createRange();e.selectNodeContents(this.element),t.removeAllRanges(),t.addRange(e)}}return}e.preventDefault();let t=this.keyToSequence(e);t&&this.onData(t)}handlePaste(e){e.preventDefault();let t=e.clipboardData?.getData(`text`);if(!t)return;let n=this.getBridge();if(n&&n.bracketedPaste()){let e=t.replace(/\x1b/g,``);this.onData(`\x1B[200~`+e+`\x1B[201~`)}else this.onData(t)}handleCompositionStart(){this.composing=!0}handleCompositionEnd(e){this.composing=!1,e.data&&this.onData(e.data),this.textarea.value=``}handleInput(){if(this.composing)return;let e=this.textarea.value;e&&(this.onData(e),this.textarea.value=``)}keyToSequence(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(e.key.length===1){let t=e.key.toLowerCase().charCodeAt(0);if(t>=97&&t<=122)return String.fromCharCode(t-96)}if(e.key===`[`)return`\x1B`;if(e.key===`\\`)return``;if(e.key===`]`)return``;if(e.key===`^`)return``;if(e.key===`_`)return``}if(e.key===`Enter`&&e.shiftKey)return`\x1B[13;2u`;if(e.key===`Tab`&&e.shiftKey)return`\x1B[Z`;let t=S[e.key];if(t)return e.altKey?`\x1B`+t:t;let n=this.getBridge(),r=(n&&n.cursorKeysApp()?x:b)[e.key];return r?e.altKey?`\x1B`+r:r:e.key.length===1&&!e.ctrlKey&&!e.metaKey?e.altKey?`\x1B`+e.key:e.key:null}},w={1:`bold`,2:`dim`,4:`italic`,8:`underline`,16:`blink`,32:`reverse`,64:`invisible`,128:`strikethrough`};function T(e){let t=[];for(let[n,r]of Object.entries(w))e&Number(n)&&t.push(r);return t}var E=27;function D(e){let t=[],n=Date.now(),r=0,i=0,a=()=>{if(r>i){let a=e.slice(i,r);a.length>0&&!/^[\x00-\x1f]+$/.test(a)&&t.push({ts:n,type:`text`,raw:a.slice(0,60)})}};for(;r=e.length)break;let s=e[r];if(s===`[`){r++;let i=``;r`||e[r]===`!`)&&(i=e[r],r++);let a=``;for(;r=48&&e.charCodeAt(r)<=59||e[r]===`:`);)a+=e[r],r++;for(;r=32&&e.charCodeAt(r)<=47;)r++;let s=``;r=64&&e.charCodeAt(r)<=126&&(s=e[r],r++);let c=e.slice(o,r),l=a?a.split(/[;:]/).map(Number).filter(e=>!isNaN(e)):[],u=s===`m`?`sgr`:`csi`;t.push({ts:n,type:u,raw:c,params:l.length>0?l:void 0,private:i||void 0,final:s})}else if(s===`]`){for(r++;r=` `&&s<=`~`?(r++,t.push({ts:n,type:`esc`,raw:e.slice(o,r),final:s})):r++;i=r}return a(),t}var O=500,k=class{constructor(){this._traces=[],this._bridge=null,this._perf={frameCount:0,totalRenderMs:0,avgRenderMs:0,maxRenderMs:0,lastDirtyRows:0}}get traces(){return this._traces}get perf(){return this._perf}setBridge(e){this._bridge=e}traceWrite(e){let t=D(typeof e==`string`?e:new TextDecoder().decode(e));for(let e of t)this._traces.push(e);this._traces.length>O&&(this._traces=this._traces.slice(-O))}recordRender(e,t){this._perf.frameCount++,this._perf.totalRenderMs+=e,this._perf.avgRenderMs=this._perf.totalRenderMs/this._perf.frameCount,e>this._perf.maxRenderMs&&(this._perf.maxRenderMs=e),this._perf.lastDirtyRows=t}resetPerf(){this._perf={frameCount:0,totalRenderMs:0,avgRenderMs:0,maxRenderMs:0,lastDirtyRows:0}}cell(e,t){if(!this._bridge)return null;let n=this._bridge.getCell(e,t);return{...n,charStr:n.char>=32?String.fromCodePoint(n.char):``,flagNames:T(n.flags)}}row(e){if(!this._bridge)return null;let t=this._bridge.getCols(),n=[];for(let r=0;r{let e=window.getSelection();(!e||e.isCollapsed)&&this.input?.focus()},this.element.addEventListener(`click`,this._onClickFocus)}async init(){try{if(this._coreOption?this.bridge=this._coreOption:this.bridge=await n.load(this.wasmUrl),this._destroyed)return this;this.bridge.init(this.cols,this.rows),this._debugEnabled&&(this.debug=new k,this.debug.setBridge(this.bridge),globalThis.__wterm=this),this._setRowHeight(),this.renderer=new y(this._container),this.renderer.setup(this.cols,this.rows),this.input=new C(this.element,e=>{this._scrollToBottom(),this.onData?this.onData(e):this.write(e)},()=>this.bridge),this.autoResize?this._setupResizeObserver():this._lockHeight(),this.input.focus(),this._initialRender()}catch(e){throw this.destroy(),Error(`wterm: failed to initialize: ${e instanceof Error?e.message:e}`)}return this}_isScrolledToBottom(){let e=this.element;return e.scrollHeight-e.scrollTop-e.clientHeight<5}_scrollToBottom(){let e=this.element,t=e.scrollHeight-e.clientHeight;if(t<=0){e.scrollTop=0;return}let n=this._rowHeight||17;e.scrollTop=Math.floor(t/n)*n}write(e){this.bridge&&(this.debug&&this.debug.traceWrite(e),this._shouldScrollToBottom=this._isScrolledToBottom(),typeof e==`string`?this.bridge.writeString(e):this.bridge.writeRaw(e),this._scheduleRender())}resize(e,t){this.bridge&&(this._shouldScrollToBottom=this._isScrolledToBottom(),this.cols=e,this.rows=t,this.bridge.resize(e,t),this.renderer?.setup(e,t),this._scheduleRender(),this.onResize&&this.onResize(e,t))}focus(){this.input?this.input.focus():this.element.focus()}_scheduleRender(){this._renderTimer??=setTimeout(()=>{this._renderTimer=null,this.rafId??=requestAnimationFrame(()=>{this.rafId=null,this._doRender()})},0)}_initialRender(){this._doRender()}_doRender(){if(!this.bridge||!this.renderer)return;let e=0,t=this.debug?performance.now():0;if(this.debug)for(let t=0;t0;this.element.classList.toggle(`has-scrollback`,n),this._shouldScrollToBottom?this._scrollToBottom():!n&&this.element.scrollTop!==0&&(this.element.scrollTop=0);let r=this.bridge.getTitle();r!==null&&this.onTitle&&this.onTitle(r);let i=this.bridge.getResponse();i!==null&&this.onData&&this.onData(i)}_lockHeight(){let e=this._rowHeight||17,t=this.rows*e,n=getComputedStyle(this.element),r=(parseFloat(n.paddingTop)||0)+(parseFloat(n.paddingBottom)||0);n.boxSizing===`border-box`&&(r+=(parseFloat(n.borderTopWidth)||0)+(parseFloat(n.borderBottomWidth)||0)),this.element.style.height=`${t+r}px`}_setRowHeight(){let e=document.createElement(`div`);e.className=`term-row`,e.style.visibility=`hidden`,e.style.position=`absolute`,e.textContent=`W`,this._container.appendChild(e);let t=e.getBoundingClientRect().height;if(e.remove(),t>0){let e=Math.ceil(t);this._rowHeight=e,this.element.style.setProperty(`--term-row-height`,`${e}px`)}}_measureCharSize(){let e=document.createElement(`div`);e.className=`term-row`,e.style.visibility=`hidden`,e.style.position=`absolute`;let t=document.createElement(`span`);t.textContent=`W`,e.appendChild(t),this._container.appendChild(e);let n=t.getBoundingClientRect().width,r=e.getBoundingClientRect().height;return e.remove(),n===0||r===0?null:(this._rowHeight=r,{charWidth:n,rowHeight:r})}_setupResizeObserver(){let e=this._measureCharSize();if(!e)return;let{charWidth:t,rowHeight:n}=e;this.resizeObserver=new ResizeObserver(e=>{let r=this._measureCharSize();r&&(t=r.charWidth,n=r.rowHeight);for(let r of e){let{width:e,height:i}=r.contentRect,a=Math.max(1,Math.floor(e/t)),o=Math.max(1,Math.floor(i/n));(a!==this.cols||o!==this.rows)&&this.resize(a,o)}}),this.resizeObserver.observe(this.element)}destroy(){this._destroyed=!0,this._renderTimer!=null&&clearTimeout(this._renderTimer),this.rafId!=null&&cancelAnimationFrame(this.rafId),this.resizeObserver&&this.resizeObserver.disconnect(),this.input&&this.input.destroy(),this.element.removeEventListener(`click`,this._onClickFocus),this.element.innerHTML=``,this.debug&&globalThis.__wterm===this&&delete globalThis.__wterm,this.debug=null}},j=new URL(`/assets/ghostty-vt-BG4Ub6dk.wasm`,``+import.meta.url).href;async function M(e){let t=await(await fetch(e??j)).arrayBuffer(),n,{instance:r}=await WebAssembly.instantiate(t,{env:{log(e,t){let r=new TextDecoder().decode(new Uint8Array(n.buffer,e,t));console.log(`[ghostty-vt]`,r)}}});return n=r.exports.memory,{exports:r.exports,instance:r}}function N(e,t){return{codepoint:e.getUint32(t,!0),fgR:e.getUint8(t+4),fgG:e.getUint8(t+5),fgB:e.getUint8(t+6),bgR:e.getUint8(t+7),bgG:e.getUint8(t+8),bgB:e.getUint8(t+9),flags:e.getUint8(t+10),width:e.getUint8(t+11),colorFlags:e.getUint8(t+12)}}function P(e,t){return e.exports.alloc_buffer(t)}function F(e,t,n){e.exports.free_buffer(t,n)}function I(e,t,n){L(e,t,new TextEncoder().encode(n))}function L(e,t,n){if(n.length===0)return;let r=P(e,n.length);r!==0&&(new Uint8Array(e.exports.memory.buffer,r,n.length).set(n),e.exports.write(t,r,n.length),F(e,r,n.length))}var R=256;function z(e,t,n){return e<<16|t<<8|n}var B={char:32,fg:R,bg:R,flags:0},V=class e{constructor(e,t){this.termPtr=0,this._viewportBufPtr=0,this._viewportBufSize=0,this._viewportView=null,this._viewportStale=!0,this._cols=0,this._rows=0,this.wasm=e,this._options=t}static async load(t={}){return new e(await M(t.wasmPath),t)}init(e,t){this._cols=e,this._rows=t;let n=this._options.scrollbackLimit??1e4;this.termPtr=this.wasm.exports.init(e,t,n),this._allocViewportBuffer(),this._invalidate()}resize(e,t){this._cols=e,this._rows=t,this.wasm.exports.resize(this.termPtr,e,t),this._allocViewportBuffer(),this._invalidate()}writeString(e){I(this.wasm,this.termPtr,e),this._invalidate()}writeRaw(e){L(this.wasm,this.termPtr,e),this._invalidate()}getCell(e,t){this._ensureViewport();let n=this._viewportView;if(!n)return B;let r=(e*this._cols+t)*16;if(r+16>this._viewportBufSize)return B;let i=N(n,r);if(i.codepoint===0&&i.flags===0&&i.colorFlags===0)return B;let a={char:i.codepoint||32,fg:R,bg:R,flags:i.flags};return i.colorFlags&1&&(a.fgRgb=z(i.fgR,i.fgG,i.fgB)),i.colorFlags&2&&(a.bgRgb=z(i.bgR,i.bgG,i.bgB)),a}isDirtyRow(e){return this._ensureViewport(),this.wasm.exports.is_dirty_row(this.termPtr,e)!==0}clearDirty(){this.wasm.exports.clear_dirty(this.termPtr),this._viewportStale=!0}getCols(){return this._cols}getRows(){return this._rows}getCursor(){return this._ensureViewport(),{row:this.wasm.exports.get_cursor_row(this.termPtr),col:this.wasm.exports.get_cursor_col(this.termPtr),visible:this.wasm.exports.get_cursor_visible(this.termPtr)!==0}}cursorKeysApp(){return this.wasm.exports.cursor_keys_app(this.termPtr)!==0}bracketedPaste(){return this.wasm.exports.bracketed_paste(this.termPtr)!==0}usingAltScreen(){return this.wasm.exports.using_alt_screen(this.termPtr)!==0}getTitle(){return null}getResponse(){let e=4096,t=P(this.wasm,e);if(t===0)return null;let n=this.wasm.exports.read_response(this.termPtr,t,e);if(n===0)return F(this.wasm,t,e),null;let r=new Uint8Array(this.wasm.exports.memory.buffer,t,n),i=new TextDecoder().decode(r);return F(this.wasm,t,e),i}getScrollbackCount(){return this.wasm.exports.get_scrollback_count(this.termPtr)}getScrollbackCell(e,t){let n=this._cols,r=n*16,i=P(this.wasm,r);if(i===0)return B;let a=this.wasm.exports.get_scrollback_line(this.termPtr,e,i,n);if(a===0||t>=a)return F(this.wasm,i,r),B;let o=N(new DataView(this.wasm.exports.memory.buffer,i,r),t*16);return F(this.wasm,i,r),{char:o.codepoint||32,fg:R,bg:R,flags:o.flags,fgRgb:z(o.fgR,o.fgG,o.fgB),bgRgb:z(o.bgR,o.bgG,o.bgB)}}getScrollbackLineLen(e){let t=this._cols,n=t*16,r=P(this.wasm,n);if(r===0)return 0;let i=this.wasm.exports.get_scrollback_line(this.termPtr,e,r,t);return F(this.wasm,r,n),i}getUnhandledSequences(){return[]}_invalidate(){this._viewportStale=!0}_allocViewportBuffer(){this._viewportBufPtr!==0&&F(this.wasm,this._viewportBufPtr,this._viewportBufSize),this._viewportBufSize=this._cols*this._rows*16,this._viewportBufPtr=P(this.wasm,this._viewportBufSize),this._viewportView=null,this._viewportStale=!0}_ensureViewport(){this._viewportStale&&=(this.wasm.exports.update(this.termPtr),this.wasm.exports.get_viewport(this.termPtr,this._viewportBufPtr),this._viewportView=new DataView(this.wasm.exports.memory.buffer,this._viewportBufPtr,this._viewportBufSize),!1)}},H=0,U=1;function W(e){let t=document.getElementById(e);if(!(t instanceof HTMLElement))throw Error(`Missing ${e} element`);return t}var G=W(`terminal`),K=W(`status`),q=new TextEncoder,J=null,Y=[];function X(e){K.textContent=e,K.classList.remove(`hidden`)}function Z(){K.textContent=``,K.classList.add(`hidden`)}function Q(e){let t=q.encode(e),n=new Uint8Array(1+t.length);return n[0]=H,n.set(t,1),n}function ee(e,t){let n=new Uint8Array(5),r=new DataView(n.buffer);return r.setUint8(0,U),r.setUint16(1,e,!1),r.setUint16(3,t,!1),n}function $(e){let t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);if(J?.readyState===WebSocket.OPEN){J.send(t);return}Y.push(new Uint8Array(t))}function te(){for(;J?.readyState===WebSocket.OPEN&&Y.length>0;){let e=Y.shift();if(e){let t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);J.send(t)}}}function ne(){return`${window.location.protocol===`https:`?`wss:`:`ws:`}//${window.location.host}/api/pty`}async function re(){let e=new A(G,{core:await V.load({scrollbackLimit:1e4}),autoResize:!0,cursorBlink:!1,onData(e){$(Q(e))},onResize(e,t){$(ee(e,t))},onTitle(e){document.title=e?`${e} - Codex Web Terminal`:`Codex Web Terminal`}});J=new WebSocket(ne()),J.binaryType=`arraybuffer`,J.addEventListener(`open`,()=>{Z(),te(),e.focus()}),J.addEventListener(`message`,t=>{if(t.data instanceof ArrayBuffer){e.write(new Uint8Array(t.data));return}if(t.data instanceof Blob){t.data.arrayBuffer().then(t=>{e.write(new Uint8Array(t))});return}e.write(String(t.data))}),J.addEventListener(`close`,()=>{X(`Terminal session disconnected.`)}),J.addEventListener(`error`,()=>{X(`Terminal connection failed.`)}),await e.init(),e.focus()}re().catch(e=>{X(`Failed to start terminal: ${e instanceof Error?e.message:String(e)}`)}); \ No newline at end of file diff --git a/codex-rs/web-server/assets/index.html b/codex-rs/web-server/assets/index.html new file mode 100644 index 0000000000..9f83a276b2 --- /dev/null +++ b/codex-rs/web-server/assets/index.html @@ -0,0 +1,20 @@ + + + + + + Codex Web Terminal + + + + +
+
+ +
+ + diff --git a/codex-rs/web-server/src/lib.rs b/codex-rs/web-server/src/lib.rs new file mode 100644 index 0000000000..1dde5a14b3 --- /dev/null +++ b/codex-rs/web-server/src/lib.rs @@ -0,0 +1,557 @@ +use anyhow::Context; +use axum::Router; +use axum::body::Body; +use axum::extract::ConnectInfo; +use axum::extract::State; +use axum::extract::ws::Message; +use axum::extract::ws::WebSocket; +use axum::extract::ws::WebSocketUpgrade; +use axum::http::HeaderMap; +use axum::http::StatusCode; +use axum::http::header; +use axum::response::IntoResponse; +use axum::response::Response; +use axum::routing::get; +use clap::Args; +use codex_utils_pty::ProcessHandle; +use codex_utils_pty::SpawnedProcess; +use codex_utils_pty::TerminalSize; +use codex_utils_pty::spawn_pty_process; +use futures::SinkExt; +use futures::StreamExt; +use include_dir::Dir; +use include_dir::include_dir; +use std::collections::HashMap; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; +use tracing::debug; +use tracing::warn; +use url::Position; +use url::Url; + +const INPUT_FRAME: u8 = 0x00; +const RESIZE_FRAME: u8 = 0x01; +const DEFAULT_TERMINAL_SIZE: TerminalSize = TerminalSize { rows: 24, cols: 80 }; +static WEBUI_ASSETS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/assets"); + +/// Run Codex in a browser-backed terminal served by the current Codex binary. +#[derive(Clone, Debug, Args)] +pub struct WebCommand { + /// Address to bind. Only loopback addresses are accepted. + #[arg(long, default_value = "127.0.0.1:0", value_name = "ADDR")] + pub listen: SocketAddr, + + /// Working directory for the Codex session. + #[arg(long, value_name = "DIR")] + pub cwd: Option, + + /// Open the served URL in the default browser. + #[arg(long)] + pub open: bool, + + /// Internal test hook: command to spawn instead of this Codex executable. + #[arg(long, hide = true, value_name = "PATH")] + pub command: Option, + + /// Internal test hook: argument for --command. May be repeated. + #[arg(long = "command-arg", hide = true, value_name = "ARG")] + pub command_args: Vec, + + /// Arguments forwarded to the inner Codex TUI. Pass them after `--`. + #[arg(last = true, value_name = "CODEX_ARGS")] + pub codex_args: Vec, +} + +#[derive(Clone, Debug)] +pub struct ServerConfig { + listen: SocketAddr, + open: bool, + command: PathBuf, + args: Vec, + cwd: PathBuf, +} + +#[derive(Clone)] +struct ServerState { + config: Arc, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ClientFrame<'a> { + Input(&'a [u8]), + Resize { cols: u16, rows: u16 }, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum FrameDecodeError { + Empty, + MalformedResize, + ZeroResize, + UnknownFrameType(u8), +} + +pub struct StaticAsset { + pub path: &'static str, + pub content_type: &'static str, + pub cache_control: &'static str, + pub bytes: &'static [u8], +} + +impl WebCommand { + pub fn into_server_config( + self, + inherited_config_overrides: Vec, + ) -> anyhow::Result { + if !self.listen.ip().is_loopback() { + anyhow::bail!("codex web only accepts loopback --listen addresses"); + } + + let cwd = match self.cwd { + Some(cwd) => cwd, + None => std::env::current_dir().context("failed to read current directory")?, + }; + let (command, args) = match self.command { + Some(command) => (command, self.command_args), + None => { + let command = std::env::current_exe().context("failed to resolve current exe")?; + let mut args = Vec::new(); + for config_override in inherited_config_overrides { + args.push("-c".to_string()); + args.push(config_override); + } + args.extend(self.codex_args); + (command, args) + } + }; + + Ok(ServerConfig { + listen: self.listen, + open: self.open, + command, + args, + cwd, + }) + } +} + +pub async fn run( + command: WebCommand, + inherited_config_overrides: Vec, +) -> anyhow::Result<()> { + let config = command.into_server_config(inherited_config_overrides)?; + let listener = TcpListener::bind(config.listen) + .await + .with_context(|| format!("failed to bind codex web listener on {}", config.listen))?; + let url = http_url_for_addr(listener.local_addr()?); + println!("Codex web listening on {url}"); + if config.open + && let Err(err) = webbrowser::open(&url) + { + eprintln!("Failed to open browser for {url}: {err}"); + } + serve_listener(listener, config).await +} + +pub async fn serve_listener(listener: TcpListener, config: ServerConfig) -> anyhow::Result<()> { + let state = ServerState { + config: Arc::new(config), + }; + let router = Router::new() + .route("/healthz", get(healthz)) + .route("/api/pty", get(pty_websocket)) + .fallback(get(static_handler)) + .with_state(state); + + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .await + .context("codex web server failed") +} + +async fn healthz() -> StatusCode { + StatusCode::OK +} + +async fn pty_websocket( + websocket: WebSocketUpgrade, + ConnectInfo(peer_addr): ConnectInfo, + State(state): State, + headers: HeaderMap, +) -> Response { + if !origin_is_allowed(&headers) { + warn!(%peer_addr, "rejecting codex web websocket due to Origin mismatch"); + return StatusCode::FORBIDDEN.into_response(); + } + + websocket + .on_upgrade(move |socket| handle_pty_socket(socket, state.config)) + .into_response() +} + +async fn static_handler(request: axum::http::Request) -> Response { + let path = request.uri().path(); + if path.starts_with("/api/") { + return StatusCode::NOT_FOUND.into_response(); + } + + match static_asset_for_path(path) { + Some(asset) => ( + [ + (header::CONTENT_TYPE, asset.content_type), + (header::CACHE_CONTROL, asset.cache_control), + ], + asset.bytes, + ) + .into_response(), + None => StatusCode::NOT_FOUND.into_response(), + } +} + +pub fn static_asset_for_path(request_path: &str) -> Option { + let asset_path = normalize_asset_path(request_path)?; + let file = WEBUI_ASSETS + .get_file(&asset_path) + .or_else(|| WEBUI_ASSETS.get_file("index.html"))?; + let path = file.path().to_str()?; + Some(StaticAsset { + path, + content_type: content_type_for_path(path), + cache_control: if path == "index.html" { + "no-store" + } else { + "public, max-age=31536000, immutable" + }, + bytes: file.contents(), + }) +} + +fn normalize_asset_path(request_path: &str) -> Option { + let trimmed = request_path.strip_prefix('/').unwrap_or(request_path); + let path = if trimmed.is_empty() { + "index.html" + } else { + trimmed + }; + if path + .split('/') + .any(|component| component.is_empty() || component == "." || component == "..") + || path.contains('\\') + { + return None; + } + Some(path.to_string()) +} + +fn content_type_for_path(path: &str) -> &'static str { + match path.rsplit_once('.').map(|(_, extension)| extension) { + Some("css") => "text/css; charset=utf-8", + Some("html") => "text/html; charset=utf-8", + Some("js") => "text/javascript; charset=utf-8", + Some("json") | Some("map") => "application/json; charset=utf-8", + Some("svg") => "image/svg+xml", + Some("wasm") => "application/wasm", + _ => "application/octet-stream", + } +} + +pub fn decode_client_frame(bytes: &[u8]) -> Result, FrameDecodeError> { + let Some(kind) = bytes.first().copied() else { + return Err(FrameDecodeError::Empty); + }; + match kind { + INPUT_FRAME => Ok(ClientFrame::Input(&bytes[1..])), + RESIZE_FRAME => { + if bytes.len() != 5 { + return Err(FrameDecodeError::MalformedResize); + } + let cols = u16::from_be_bytes([bytes[1], bytes[2]]); + let rows = u16::from_be_bytes([bytes[3], bytes[4]]); + if cols == 0 || rows == 0 { + return Err(FrameDecodeError::ZeroResize); + } + Ok(ClientFrame::Resize { cols, rows }) + } + other => Err(FrameDecodeError::UnknownFrameType(other)), + } +} + +async fn handle_pty_socket(socket: WebSocket, config: Arc) { + let spawned = match spawn_codex_pty(config.as_ref()).await { + Ok(spawned) => spawned, + Err(err) => { + let mut socket = socket; + let message = format!("Failed to start PTY: {err}\r\n"); + let _ = socket + .send(Message::Binary(message.into_bytes().into())) + .await; + let _ = socket.close().await; + return; + } + }; + + bridge_socket_to_pty(socket, spawned).await; +} + +async fn spawn_codex_pty(config: &ServerConfig) -> anyhow::Result { + let command = config + .command + .to_str() + .ok_or_else(|| anyhow::anyhow!("codex web command path is not valid UTF-8"))?; + spawn_pty_process( + command, + &config.args, + &config.cwd, + &child_environment(), + /*arg0*/ &None, + DEFAULT_TERMINAL_SIZE, + ) + .await + .with_context(|| format!("failed to spawn {}", config.command.display())) +} + +async fn bridge_socket_to_pty(socket: WebSocket, spawned: SpawnedProcess) { + let SpawnedProcess { + session, + mut stdout_rx, + stderr_rx: _, + mut exit_rx, + } = spawned; + let session = Arc::new(session); + let writer = session.writer_sender(); + let resize_session = Arc::clone(&session); + let (mut websocket_writer, mut websocket_reader) = socket.split(); + + let mut outbound_task: JoinHandle<()> = tokio::spawn(async move { + loop { + tokio::select! { + output = stdout_rx.recv() => { + let Some(output) = output else { + break; + }; + if websocket_writer.send(Message::Binary(output.into())).await.is_err() { + break; + } + } + exit = &mut exit_rx => { + let code = exit.unwrap_or(-1); + let message = format!("\r\n[process exited: {code}]\r\n"); + let _ = websocket_writer + .send(Message::Binary(message.into_bytes().into())) + .await; + let _ = websocket_writer.close().await; + break; + } + } + } + }); + + let mut inbound_task: JoinHandle<()> = tokio::spawn(async move { + while let Some(message) = websocket_reader.next().await { + match message { + Ok(Message::Binary(bytes)) => match decode_client_frame(&bytes) { + Ok(ClientFrame::Input(input)) => { + if writer.send(input.to_vec()).await.is_err() { + break; + } + } + Ok(ClientFrame::Resize { cols, rows }) => { + if let Err(err) = resize_session.resize(TerminalSize { rows, cols }) { + debug!("failed to resize codex web PTY: {err}"); + } + } + Err(err) => { + debug!("ignoring malformed codex web frame: {err:?}"); + } + }, + Ok(Message::Close(_)) | Err(_) => break, + Ok(Message::Text(_) | Message::Ping(_) | Message::Pong(_)) => {} + } + } + }); + + tokio::select! { + _ = &mut outbound_task => { + inbound_task.abort(); + } + _ = &mut inbound_task => { + outbound_task.abort(); + } + } + terminate_process(&session); +} + +fn terminate_process(session: &ProcessHandle) { + session.terminate(); +} + +fn child_environment() -> HashMap { + let mut env: HashMap = std::env::vars().collect(); + env.insert("TERM".to_string(), "xterm-256color".to_string()); + env.insert("COLORTERM".to_string(), "truecolor".to_string()); + env.insert("TERM_PROGRAM".to_string(), "wterm".to_string()); + env.insert( + "CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT".to_string(), + "1".to_string(), + ); + env +} + +fn origin_is_allowed(headers: &HeaderMap) -> bool { + let Some(origin) = headers.get(header::ORIGIN) else { + return true; + }; + let Ok(origin) = origin.to_str() else { + return false; + }; + let Ok(origin) = Url::parse(origin) else { + return false; + }; + let Some(host) = headers.get(header::HOST) else { + return false; + }; + let Ok(host) = host.to_str() else { + return false; + }; + origin[Position::BeforeHost..Position::AfterPort].eq_ignore_ascii_case(host) +} + +fn http_url_for_addr(addr: SocketAddr) -> String { + match addr.ip() { + IpAddr::V4(ip) => format!("http://{ip}:{}", addr.port()), + IpAddr::V6(ip) => format!("http://[{ip}]:{}", addr.port()), + } +} + +pub async fn spawn_for_test( + command: PathBuf, + args: Vec, +) -> anyhow::Result<(String, oneshot::Sender<()>, JoinHandle>)> { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let url = http_url_for_addr(listener.local_addr()?); + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let config = ServerConfig { + listen: listener.local_addr()?, + open: false, + command, + args, + cwd: std::env::current_dir()?, + }; + let handle = tokio::spawn(async move { + let state = ServerState { + config: Arc::new(config), + }; + let router = Router::new() + .route("/healthz", get(healthz)) + .route("/api/pty", get(pty_websocket)) + .fallback(get(static_handler)) + .with_state(state); + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(async move { + let _ = shutdown_rx.await; + }) + .await + .context("codex web test server failed") + }); + Ok((url, shutdown_tx, handle)) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn decodes_input_frames() { + let frame = [INPUT_FRAME, b'h', b'i']; + assert_eq!(decode_client_frame(&frame), Ok(ClientFrame::Input(b"hi"))); + } + + #[test] + fn decodes_resize_frames() { + let frame = [RESIZE_FRAME, 0, 120, 0, 40]; + assert_eq!( + decode_client_frame(&frame), + Ok(ClientFrame::Resize { + cols: 120, + rows: 40 + }) + ); + } + + #[test] + fn rejects_invalid_frames() { + assert_eq!(decode_client_frame(&[]), Err(FrameDecodeError::Empty)); + assert_eq!( + decode_client_frame(&[RESIZE_FRAME, 0, 80]), + Err(FrameDecodeError::MalformedResize) + ); + assert_eq!( + decode_client_frame(&[RESIZE_FRAME, 0, 0, 0, 24]), + Err(FrameDecodeError::ZeroResize) + ); + assert_eq!( + decode_client_frame(&[9]), + Err(FrameDecodeError::UnknownFrameType(9)) + ); + } + + #[test] + fn serves_index_for_root_and_spa_routes() { + let root = static_asset_for_path("/").expect("root asset"); + let route = static_asset_for_path("/thread/123").expect("route asset"); + + assert_eq!(root.path, "index.html"); + assert_eq!(route.path, "index.html"); + assert_eq!(root.content_type, "text/html; charset=utf-8"); + assert_eq!(root.cache_control, "no-store"); + } + + #[test] + fn rejects_path_traversal() { + assert!(static_asset_for_path("/../Cargo.toml").is_none()); + assert!(static_asset_for_path("/assets\\index.js").is_none()); + } + + #[cfg(unix)] + #[tokio::test] + async fn websocket_bridges_pty_output() -> anyhow::Result<()> { + let (url, shutdown, handle) = spawn_for_test( + PathBuf::from("/bin/sh"), + vec!["-c".to_string(), "printf READY; cat".to_string()], + ) + .await?; + let ws_url = format!("{url}/api/pty").replace("http://", "ws://"); + let (mut socket, _) = tokio_tungstenite::connect_async(&ws_url).await?; + let mut saw_ready = false; + + for _ in 0..8 { + if let Some(message) = socket.next().await { + let message = message?; + if message + .into_data() + .windows("READY".len()) + .any(|w| w == b"READY") + { + saw_ready = true; + break; + } + } + } + + let _ = socket.close(None).await; + let _ = shutdown.send(()); + let _ = handle.await?; + assert!(saw_ready); + Ok(()) + } +} diff --git a/codex-webui/index.html b/codex-webui/index.html new file mode 100644 index 0000000000..f344b22d8a --- /dev/null +++ b/codex-webui/index.html @@ -0,0 +1,19 @@ + + + + + + Codex Web Terminal + + +
+
+ +
+ + + diff --git a/codex-webui/package.json b/codex-webui/package.json new file mode 100644 index 0000000000..1e35aef360 --- /dev/null +++ b/codex-webui/package.json @@ -0,0 +1,30 @@ +{ + "name": "@openai/codex-webui", + "version": "0.0.0-dev", + "private": true, + "type": "module", + "scripts": { + "dev": "node server.mjs --dev", + "start": "node server.mjs", + "build": "vite build", + "build:rust-assets": "pnpm build && node scripts/sync-rust-assets.mjs", + "check:rust-assets": "pnpm build && node scripts/sync-rust-assets.mjs --check", + "typecheck": "tsc --noEmit", + "test": "node --test tests/*.test.mjs" + }, + "dependencies": { + "@tailwindcss/vite": "4.2.4", + "@wterm/dom": "0.3.0", + "@wterm/ghostty": "0.3.0", + "node-pty": "1.2.0-beta.12", + "tailwindcss": "4.2.4", + "vite": "8.0.10", + "ws": "8.20.0" + }, + "devDependencies": { + "typescript": "6.0.3" + }, + "engines": { + "node": ">=22" + } +} diff --git a/codex-webui/protocol.mjs b/codex-webui/protocol.mjs new file mode 100644 index 0000000000..bcdd73f7c8 --- /dev/null +++ b/codex-webui/protocol.mjs @@ -0,0 +1,28 @@ +export const INPUT_FRAME = 0x00; +export const RESIZE_FRAME = 0x01; + +export function decodeClientFrame(frame) { + const data = Buffer.isBuffer(frame) ? frame : Buffer.from(frame); + if (data.length === 0) { + return { type: "invalid", reason: "empty frame" }; + } + + const kind = data.readUInt8(0); + if (kind === INPUT_FRAME) { + return { type: "input", data: data.subarray(1).toString("utf8") }; + } + + if (kind === RESIZE_FRAME) { + if (data.length !== 5) { + return { type: "invalid", reason: "malformed resize frame" }; + } + const cols = data.readUInt16BE(1); + const rows = data.readUInt16BE(3); + if (cols === 0 || rows === 0) { + return { type: "invalid", reason: "resize dimensions must be positive" }; + } + return { type: "resize", cols, rows }; + } + + return { type: "invalid", reason: `unknown frame type ${kind}` }; +} diff --git a/codex-webui/scripts/sync-rust-assets.mjs b/codex-webui/scripts/sync-rust-assets.mjs new file mode 100644 index 0000000000..72344a5770 --- /dev/null +++ b/codex-webui/scripts/sync-rust-assets.mjs @@ -0,0 +1,85 @@ +#!/usr/bin/env node +import { createHash } from "node:crypto"; +import { + cpSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, +} from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const packageRoot = resolve(dirname(__filename), ".."); +const repoRoot = resolve(packageRoot, ".."); +const distRoot = join(packageRoot, "dist"); +const rustAssetsRoot = join(repoRoot, "codex-rs", "web-server", "assets"); +const check = process.argv.includes("--check"); + +if (!existsSync(join(distRoot, "index.html"))) { + throw new Error("Missing dist/index.html. Run the codex-webui build first."); +} + +if (check) { + const diff = diffTrees(distRoot, rustAssetsRoot); + if (diff.length > 0) { + throw new Error( + `codex web assets are out of date:\n${diff.map((item) => ` ${item}`).join("\n")}`, + ); + } + console.log("codex web Rust assets are up to date."); +} else { + rmSync(rustAssetsRoot, { force: true, recursive: true }); + mkdirSync(rustAssetsRoot, { recursive: true }); + cpSync(distRoot, rustAssetsRoot, { recursive: true }); + console.log( + `Synced ${relative(repoRoot, distRoot)} to ${relative(repoRoot, rustAssetsRoot)}.`, + ); +} + +function diffTrees(leftRoot, rightRoot) { + const leftFiles = listFiles(leftRoot); + const rightFiles = listFiles(rightRoot); + const allFiles = new Set([...leftFiles.keys(), ...rightFiles.keys()]); + const diff = []; + + for (const file of [...allFiles].sort()) { + const left = leftFiles.get(file); + const right = rightFiles.get(file); + if (!left) { + diff.push(`unexpected ${file}`); + } else if (!right) { + diff.push(`missing ${file}`); + } else if (left !== right) { + diff.push(`changed ${file}`); + } + } + + return diff; +} + +function listFiles(root) { + const files = new Map(); + if (!existsSync(root)) { + return files; + } + walk(root, root, files); + return files; +} + +function walk(root, dir, files) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) { + walk(root, path, files); + } else if (entry.isFile()) { + files.set(relative(root, path), sha256(path)); + } + } +} + +function sha256(path) { + return createHash("sha256").update(readFileSync(path)).digest("hex"); +} diff --git a/codex-webui/server.mjs b/codex-webui/server.mjs new file mode 100644 index 0000000000..dee1eb9cea --- /dev/null +++ b/codex-webui/server.mjs @@ -0,0 +1,309 @@ +#!/usr/bin/env node +import { createServer } from "node:http"; +import { createReadStream, existsSync } from "node:fs"; +import { dirname, extname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import pty from "node-pty"; +import { WebSocketServer } from "ws"; +import { decodeClientFrame } from "./protocol.mjs"; + +const __filename = fileURLToPath(import.meta.url); +const packageRoot = dirname(__filename); +const repoRoot = resolve(packageRoot, ".."); + +const MIME_TYPES = { + ".css": "text/css; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".map": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".wasm": "application/wasm", +}; + +const SERVER_FLAGS = new Set([ + "--dev", + "--shell", + "--host", + "--port", + "--cwd", + "--codex-bin", +]); + +export function parseArgs(argv) { + const options = { + dev: false, + host: "127.0.0.1", + port: 4321, + cwd: repoRoot, + codexBin: null, + shell: false, + codexArgs: [], + }; + + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === "--") { + if (SERVER_FLAGS.has(argv[index + 1])) { + continue; + } + options.codexArgs = argv.slice(index + 1); + break; + } + if (arg === "--dev") { + options.dev = true; + } else if (arg === "--shell") { + options.shell = true; + } else if (arg === "--host") { + options.host = requireValue(argv, (index += 1), arg); + } else if (arg === "--port") { + options.port = Number.parseInt(requireValue(argv, (index += 1), arg), 10); + if (!Number.isFinite(options.port) || options.port <= 0) { + throw new Error("--port must be a positive integer"); + } + } else if (arg === "--cwd") { + options.cwd = resolve(requireValue(argv, (index += 1), arg)); + } else if (arg === "--codex-bin") { + options.codexBin = resolve(requireValue(argv, (index += 1), arg)); + } else { + throw new Error(`Unknown argument: ${arg}`); + } + } + + return options; +} + +function requireValue(argv, index, flag) { + const value = argv[index]; + if (!value) { + throw new Error(`${flag} requires a value`); + } + return value; +} + +function commandFor(options) { + if (options.shell) { + return { + command: process.env.SHELL || "/bin/sh", + args: ["-l"], + }; + } + + if (options.codexBin) { + return { + command: options.codexBin, + args: options.codexArgs, + }; + } + + const debugCodex = join( + repoRoot, + "codex-rs", + "target", + "debug", + process.platform === "win32" ? "codex.exe" : "codex", + ); + if (existsSync(debugCodex)) { + return { + command: debugCodex, + args: options.codexArgs, + }; + } + + return { + command: "cargo", + args: [ + "run", + "--manifest-path", + join(repoRoot, "codex-rs", "Cargo.toml"), + "--bin", + "codex", + "--", + ...options.codexArgs, + ], + }; +} + +function createPty(options) { + const { command, args } = commandFor(options); + return pty.spawn(command, args, { + name: "xterm-256color", + cols: 80, + rows: 24, + cwd: options.cwd, + env: { + ...process.env, + TERM: "xterm-256color", + COLORTERM: "truecolor", + TERM_PROGRAM: "wterm", + CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT: "1", + }, + }); +} + +function isAllowedOrigin(request) { + const origin = request.headers.origin; + if (!origin) { + return true; + } + + try { + return new URL(origin).host === request.headers.host; + } catch { + return false; + } +} + +function sendHttp(response, status, body, headers = {}) { + response.writeHead(status, { + "content-type": "text/plain; charset=utf-8", + "cache-control": "no-store", + ...headers, + }); + response.end(body); +} + +async function serveStatic(request, response) { + const distRoot = join(packageRoot, "dist"); + const url = new URL(request.url ?? "/", "http://localhost"); + const pathname = decodeURIComponent(url.pathname); + const relativePath = pathname === "/" ? "index.html" : pathname.slice(1); + const resolvedPath = resolve(distRoot, relativePath); + + if (!resolvedPath.startsWith(`${distRoot}/`) && resolvedPath !== distRoot) { + sendHttp(response, 403, "Forbidden"); + return; + } + + const filePath = existsSync(resolvedPath) + ? resolvedPath + : join(distRoot, "index.html"); + const contentType = + MIME_TYPES[extname(filePath)] || "application/octet-stream"; + response.writeHead(200, { + "content-type": contentType, + "cache-control": filePath.endsWith("index.html") + ? "no-store" + : "public, max-age=31536000, immutable", + }); + createReadStream(filePath).pipe(response); +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + const server = createServer(); + const wss = new WebSocketServer({ noServer: true }); + + if (options.dev) { + const { createServer: createViteServer } = await import("vite"); + const vite = await createViteServer({ + root: packageRoot, + server: { middlewareMode: true }, + appType: "spa", + }); + server.on("request", (request, response) => { + vite.middlewares(request, response, () => { + sendHttp(response, 404, "Not found"); + }); + }); + } else { + server.on("request", (request, response) => { + if (request.url === "/healthz") { + sendHttp(response, 200, "ok"); + return; + } + + if (!existsSync(join(packageRoot, "dist", "index.html"))) { + sendHttp( + response, + 500, + "Missing dist/. Run pnpm --filter @openai/codex-webui build first.", + ); + return; + } + + void serveStatic(request, response).catch((error) => { + sendHttp( + response, + 500, + error instanceof Error ? error.message : String(error), + ); + }); + }); + } + + server.on("upgrade", (request, socket, head) => { + const url = new URL(request.url ?? "/", "http://localhost"); + if (url.pathname !== "/api/pty" || !isAllowedOrigin(request)) { + socket.write("HTTP/1.1 403 Forbidden\r\n\r\n"); + socket.destroy(); + return; + } + + wss.handleUpgrade(request, socket, head, (websocket) => { + wss.emit("connection", websocket, request); + }); + }); + + wss.on("connection", (websocket) => { + let child; + try { + child = createPty(options); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (websocket.readyState === websocket.OPEN) { + websocket.send(Buffer.from(`Failed to start PTY: ${message}\r\n`)); + websocket.close(); + } + return; + } + + const output = child.onData((data) => { + if (websocket.readyState === websocket.OPEN) { + websocket.send(Buffer.from(data, "utf8")); + } + }); + + child.onExit(({ exitCode, signal }) => { + if (websocket.readyState === websocket.OPEN) { + websocket.send( + Buffer.from( + `\r\n[process exited: ${signal ?? exitCode}]\r\n`, + "utf8", + ), + ); + websocket.close(); + } + }); + + websocket.on("message", (data) => { + const frame = decodeClientFrame(data); + if (frame.type === "input") { + child.write(frame.data); + } else if (frame.type === "resize") { + child.resize(frame.cols, frame.rows); + } + }); + + websocket.on("close", () => { + output.dispose(); + child.kill(); + }); + }); + + await new Promise((resolveListen) => { + server.listen(options.port, options.host, resolveListen); + }); + + const address = server.address(); + const port = + typeof address === "object" && address ? address.port : options.port; + console.log(`Codex web terminal listening on http://${options.host}:${port}`); +} + +if (process.argv[1] === __filename) { + main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); + }); +} diff --git a/codex-webui/src/main.ts b/codex-webui/src/main.ts new file mode 100644 index 0000000000..6a048c24e7 --- /dev/null +++ b/codex-webui/src/main.ts @@ -0,0 +1,138 @@ +import { WTerm } from "@wterm/dom"; +import { GhosttyCore } from "@wterm/ghostty"; +import "./styles.css"; + +const INPUT_FRAME = 0x00; +const RESIZE_FRAME = 0x01; + +function requireElement(id: string): HTMLElement { + const element = document.getElementById(id); + if (!(element instanceof HTMLElement)) { + throw new Error(`Missing ${id} element`); + } + return element; +} + +const terminalElement = requireElement("terminal"); +const statusElement = requireElement("status"); +const encoder = new TextEncoder(); +let socket: WebSocket | null = null; +const pendingFrames: Uint8Array[] = []; + +function showStatus(message: string): void { + statusElement.textContent = message; + statusElement.classList.remove("hidden"); +} + +function clearStatus(): void { + statusElement.textContent = ""; + statusElement.classList.add("hidden"); +} + +function encodeInput(data: string): Uint8Array { + const encoded = encoder.encode(data); + const frame = new Uint8Array(1 + encoded.length); + frame[0] = INPUT_FRAME; + frame.set(encoded, 1); + return frame; +} + +function encodeResize(cols: number, rows: number): Uint8Array { + const frame = new Uint8Array(5); + const view = new DataView(frame.buffer); + view.setUint8(0, RESIZE_FRAME); + view.setUint16(1, cols, false); + view.setUint16(3, rows, false); + return frame; +} + +function sendFrame(frame: Uint8Array): void { + const payload = frame.buffer.slice( + frame.byteOffset, + frame.byteOffset + frame.byteLength, + ) as ArrayBuffer; + if (socket?.readyState === WebSocket.OPEN) { + socket.send(payload); + return; + } + pendingFrames.push(new Uint8Array(payload)); +} + +function flushPendingFrames(): void { + while (socket?.readyState === WebSocket.OPEN && pendingFrames.length > 0) { + const frame = pendingFrames.shift(); + if (frame) { + const payload = frame.buffer.slice( + frame.byteOffset, + frame.byteOffset + frame.byteLength, + ) as ArrayBuffer; + socket.send(payload); + } + } +} + +function websocketUrl(): string { + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + return `${protocol}//${window.location.host}/api/pty`; +} + +async function main(): Promise { + const core = await GhosttyCore.load({ scrollbackLimit: 10000 }); + const term = new WTerm(terminalElement, { + core, + autoResize: true, + cursorBlink: false, + onData(data) { + sendFrame(encodeInput(data)); + }, + onResize(cols, rows) { + sendFrame(encodeResize(cols, rows)); + }, + onTitle(title) { + document.title = title + ? `${title} - Codex Web Terminal` + : "Codex Web Terminal"; + }, + }); + + socket = new WebSocket(websocketUrl()); + socket.binaryType = "arraybuffer"; + + socket.addEventListener("open", () => { + clearStatus(); + flushPendingFrames(); + term.focus(); + }); + + socket.addEventListener("message", (event) => { + if (event.data instanceof ArrayBuffer) { + term.write(new Uint8Array(event.data)); + return; + } + + if (event.data instanceof Blob) { + void event.data.arrayBuffer().then((buffer) => { + term.write(new Uint8Array(buffer)); + }); + return; + } + + term.write(String(event.data)); + }); + + socket.addEventListener("close", () => { + showStatus("Terminal session disconnected."); + }); + + socket.addEventListener("error", () => { + showStatus("Terminal connection failed."); + }); + + await term.init(); + term.focus(); +} + +main().catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + showStatus(`Failed to start terminal: ${message}`); +}); diff --git a/codex-webui/src/styles.css b/codex-webui/src/styles.css new file mode 100644 index 0000000000..7beb3600f4 --- /dev/null +++ b/codex-webui/src/styles.css @@ -0,0 +1,19 @@ +@import "tailwindcss"; +@import "@wterm/dom/css"; + +html, +body { + margin: 0; + min-height: 100%; +} + +body { + overflow: hidden; +} + +#terminal { + padding: max(env(safe-area-inset-top), 0.5rem) + max(env(safe-area-inset-right), 0.5rem) + max(env(safe-area-inset-bottom), 0.5rem) + max(env(safe-area-inset-left), 0.5rem); +} diff --git a/codex-webui/src/vite-env.d.ts b/codex-webui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/codex-webui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/codex-webui/tests/protocol.test.mjs b/codex-webui/tests/protocol.test.mjs new file mode 100644 index 0000000000..738fe621d6 --- /dev/null +++ b/codex-webui/tests/protocol.test.mjs @@ -0,0 +1,43 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { decodeClientFrame, INPUT_FRAME, RESIZE_FRAME } from "../protocol.mjs"; + +test("decodes input frames", () => { + const frame = Buffer.concat([ + Buffer.from([INPUT_FRAME]), + Buffer.from("hello"), + ]); + assert.deepEqual(decodeClientFrame(frame), { type: "input", data: "hello" }); +}); + +test("decodes resize frames", () => { + const frame = Buffer.alloc(5); + frame.writeUInt8(RESIZE_FRAME, 0); + frame.writeUInt16BE(120, 1); + frame.writeUInt16BE(40, 3); + + assert.deepEqual(decodeClientFrame(frame), { + type: "resize", + cols: 120, + rows: 40, + }); +}); + +test("rejects malformed resize frames", () => { + assert.deepEqual(decodeClientFrame(Buffer.from([RESIZE_FRAME, 0, 80])), { + type: "invalid", + reason: "malformed resize frame", + }); +}); + +test("rejects zero resize dimensions", () => { + const frame = Buffer.alloc(5); + frame.writeUInt8(RESIZE_FRAME, 0); + frame.writeUInt16BE(0, 1); + frame.writeUInt16BE(24, 3); + + assert.deepEqual(decodeClientFrame(frame), { + type: "invalid", + reason: "resize dimensions must be positive", + }); +}); diff --git a/codex-webui/tests/server.test.mjs b/codex-webui/tests/server.test.mjs new file mode 100644 index 0000000000..0781e0ee7e --- /dev/null +++ b/codex-webui/tests/server.test.mjs @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { parseArgs } from "../server.mjs"; + +test("parses pnpm script argument separator before server flags", () => { + const options = parseArgs(["--dev", "--", "--port", "4322", "--shell"]); + assert.equal(options.dev, true); + assert.equal(options.port, 4322); + assert.equal(options.shell, true); + assert.deepEqual(options.codexArgs, []); +}); + +test("uses separator before non-server flags as codex args", () => { + const options = parseArgs(["--dev", "--", "--model", "gpt-test"]); + assert.deepEqual(options.codexArgs, ["--model", "gpt-test"]); +}); diff --git a/codex-webui/tsconfig.json b/codex-webui/tsconfig.json new file mode 100644 index 0000000000..1d1f53fd1f --- /dev/null +++ b/codex-webui/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": false, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true + }, + "include": ["src"] +} diff --git a/codex-webui/vite.config.ts b/codex-webui/vite.config.ts new file mode 100644 index 0000000000..2f4c184c1c --- /dev/null +++ b/codex-webui/vite.config.ts @@ -0,0 +1,6 @@ +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [tailwindcss()], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03ff4643ab..2d2ca978d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,34 @@ importers: codex-rs/responses-api-proxy/npm: {} + codex-webui: + dependencies: + '@tailwindcss/vite': + specifier: 4.2.4 + version: 4.2.4(vite@8.0.10(@types/node@20.19.18)(jiti@2.6.1)(yaml@2.8.1)) + '@wterm/dom': + specifier: 0.3.0 + version: 0.3.0 + '@wterm/ghostty': + specifier: 0.3.0 + version: 0.3.0 + node-pty: + specifier: 1.2.0-beta.12 + version: 1.2.0-beta.12 + tailwindcss: + specifier: 4.2.4 + version: 4.2.4 + vite: + specifier: 8.0.10 + version: 8.0.10(@types/node@20.19.18)(jiti@2.6.1)(yaml@2.8.1) + ws: + specifier: 8.20.0 + version: 8.20.0 + devDependencies: + typescript: + specifier: 6.0.3 + version: 6.0.3 + sdk/typescript: devDependencies: '@modelcontextprotocol/sdk': @@ -44,16 +72,16 @@ importers: version: 20.19.18 eslint: specifier: ^9.36.0 - version: 9.36.0 + version: 9.36.0(jiti@2.6.1) eslint-config-prettier: specifier: ^9.1.2 - version: 9.1.2(eslint@9.36.0) + version: 9.1.2(eslint@9.36.0(jiti@2.6.1)) eslint-plugin-jest: specifier: ^29.0.1 - version: 29.0.1(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2) + version: 29.0.1(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2) eslint-plugin-node-import: specifier: ^1.0.5 - version: 1.0.5(eslint@9.36.0) + version: 1.0.5(eslint@9.36.0(jiti@2.6.1)) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)) @@ -71,13 +99,13 @@ importers: version: 10.9.2(@types/node@20.19.18)(typescript@5.9.2) tsup: specifier: ^8.5.0 - version: 8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.1)(postcss@8.5.10)(typescript@5.9.2)(yaml@2.8.1) typescript: specifier: ^5.9.2 version: 5.9.2 typescript-eslint: specifier: ^8.45.0 - version: 8.45.0(eslint@9.36.0)(typescript@5.9.2) + version: 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) zod: specifier: ^3.24.2 version: 3.25.76 @@ -256,6 +284,15 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} @@ -579,6 +616,12 @@ packages: '@cfworker/json-schema': optional: true + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -591,10 +634,111 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -742,6 +886,100 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -754,6 +992,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -858,6 +1099,15 @@ packages: resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@wterm/core@0.3.0': + resolution: {integrity: sha512-aQ73QBP+eyA3kcn31mA7DmAi7qmEsQijZdmvgwxtSjCi2248EAOZgtkGDpjlspfrxyVxQbUl/IF+gCVcXt4nZQ==} + + '@wterm/dom@0.3.0': + resolution: {integrity: sha512-OiUl7reGSlW02yb5wGXMplMZhW2HBkBSFxvbFj15I832UK0QFIHRvhE2l6Xgae8ROn9bNLaALoKDFvcUuz80iQ==} + + '@wterm/ghostty@0.3.0': + resolution: {integrity: sha512-TDjEmUSRD7IaFxq31P7+I3Inkp5mHCfBfO6LYj3/vbLbWOkOZOKPhjAcZ36AYFHe9Q0HLCFvLWmrxIO3aGCjDA==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1147,6 +1397,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -1186,6 +1440,10 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + enhanced-resolve@5.21.0: + resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==} + engines: {node: '>=10.13.0'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1731,6 +1989,10 @@ packages: node-notifier: optional: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} @@ -1792,6 +2054,80 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1829,6 +2165,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -1912,9 +2251,15 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-pty@1.2.0-beta.12: + resolution: {integrity: sha512-uExTCG/4VmSJa4+TjxFwPXv8BfacmfFEBL6JpxCMDghcwqzvD0yTcGmZ1fKOK6HY33tp0CelLblqTECJizc+Yw==} + node-releases@2.0.21: resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} @@ -2053,8 +2398,8 @@ packages: yaml: optional: true - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2149,6 +2494,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2301,6 +2651,13 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -2319,6 +2676,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -2392,6 +2753,9 @@ packages: '@swc/wasm': optional: true + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.5.0: resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} engines: {node: '>=18'} @@ -2443,6 +2807,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} @@ -2478,6 +2847,49 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -2514,6 +2926,18 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2750,6 +3174,22 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.10': optional: true @@ -2828,9 +3268,9 @@ snapshots: '@esbuild/win32-x64@0.25.10': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.1))': dependencies: - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -3114,6 +3554,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3126,9 +3573,62 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@oxc-project/types@0.127.0': {} + '@pkgjs/parseargs@0.11.0': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -3214,6 +3714,74 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.0 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@20.19.18)(jiti@2.6.1)(yaml@2.8.1))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 8.0.10(@types/node@20.19.18)(jiti@2.6.1)(yaml@2.8.1) + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -3222,6 +3790,11 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.4 @@ -3278,15 +3851,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.45.0 - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -3295,14 +3868,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2)': + '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.45.0 debug: 4.4.3 - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -3325,13 +3898,13 @@ snapshots: dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0)(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 8.45.0 '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) debug: 4.4.3 - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: @@ -3355,13 +3928,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.45.0(eslint@9.36.0)(typescript@5.9.2)': + '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -3371,6 +3944,16 @@ snapshots: '@typescript-eslint/types': 8.45.0 eslint-visitor-keys: 4.2.1 + '@wterm/core@0.3.0': {} + + '@wterm/dom@0.3.0': + dependencies: + '@wterm/core': 0.3.0 + + '@wterm/ghostty@0.3.0': + dependencies: + '@wterm/core': 0.3.0 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -3659,6 +4242,8 @@ snapshots: depd@2.0.0: {} + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} diff-sequences@29.6.3: {} @@ -3685,6 +4270,11 @@ snapshots: encodeurl@2.0.0: {} + enhanced-resolve@5.21.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -3734,24 +4324,24 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.2(eslint@9.36.0): + eslint-config-prettier@9.1.2(eslint@9.36.0(jiti@2.6.1)): dependencies: - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) - eslint-plugin-jest@29.0.1(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2): + eslint-plugin-jest@29.0.1(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) - eslint: 9.36.0 + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) jest: 29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-node-import@1.0.5(eslint@9.36.0): + eslint-plugin-node-import@1.0.5(eslint@9.36.0(jiti@2.6.1)): dependencies: - eslint: 9.36.0 + eslint: 9.36.0(jiti@2.6.1) eslint-scope@8.4.0: dependencies: @@ -3762,9 +4352,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.36.0: + eslint@9.36.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 @@ -3799,6 +4389,8 @@ snapshots: minimatch: 3.1.4 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -4478,6 +5070,8 @@ snapshots: - supports-color - ts-node + jiti@2.6.1: {} + jose@6.1.3: {} joycon@3.1.1: {} @@ -4522,6 +5116,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -4552,6 +5195,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@4.0.0: dependencies: semver: 7.7.2 @@ -4612,8 +5259,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.11: - optional: true + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -4621,8 +5267,14 @@ snapshots: neo-async@2.6.2: {} + node-addon-api@7.1.1: {} + node-int64@0.4.0: {} + node-pty@1.2.0-beta.12: + dependencies: + node-addon-api: 7.1.1 + node-releases@2.0.21: {} normalize-path@3.0.0: {} @@ -4726,19 +5378,19 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 - postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.8.1): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.10)(yaml@2.8.1): dependencies: lilconfig: 3.1.3 optionalDependencies: - postcss: 8.5.6 + jiti: 2.6.1 + postcss: 8.5.10 yaml: 2.8.1 - postcss@8.5.6: + postcss@8.5.10: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - optional: true prelude-ls@1.2.1: {} @@ -4811,6 +5463,27 @@ snapshots: reusify@1.1.0: {} + rolldown@1.0.0-rc.17: + dependencies: + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -4931,8 +5604,7 @@ snapshots: slash@3.0.0: {} - source-map-js@1.2.1: - optional: true + source-map-js@1.2.1: {} source-map-support@0.5.13: dependencies: @@ -5006,6 +5678,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwindcss@4.2.4: {} + + tapable@2.3.3: {} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -5027,6 +5703,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -5090,7 +5771,10 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsup@8.5.0(postcss@8.5.6)(typescript@5.9.2)(yaml@2.8.1): + tslib@2.8.1: + optional: true + + tsup@8.5.0(jiti@2.6.1)(postcss@8.5.10)(typescript@5.9.2)(yaml@2.8.1): dependencies: bundle-require: 5.1.0(esbuild@0.25.10) cac: 6.7.14 @@ -5101,7 +5785,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.6)(yaml@2.8.1) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.10)(yaml@2.8.1) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.8.0-beta.0 @@ -5110,7 +5794,7 @@ snapshots: tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.6 + postcss: 8.5.10 typescript: 5.9.2 transitivePeerDependencies: - jiti @@ -5134,19 +5818,21 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 - typescript-eslint@8.45.0(eslint@9.36.0)(typescript@5.9.2): + typescript-eslint@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) - eslint: 9.36.0 + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.2) + eslint: 9.36.0(jiti@2.6.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color typescript@5.9.2: {} + typescript@6.0.3: {} + ufo@1.6.1: {} uglify-js@3.19.3: @@ -5176,6 +5862,19 @@ snapshots: vary@1.1.2: {} + vite@8.0.10(@types/node@20.19.18)(jiti@2.6.1)(yaml@2.8.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.10 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 20.19.18 + fsevents: 2.3.3 + jiti: 2.6.1 + yaml: 2.8.1 + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -5215,6 +5914,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + ws@8.20.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7f5e2d915e..b21699b056 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - codex-cli + - codex-webui - codex-rs/responses-api-proxy/npm - sdk/typescript @@ -7,11 +8,15 @@ ignoredBuiltDependencies: - esbuild minimumReleaseAge: 10080 -minimumReleaseAgeExclude: [] +minimumReleaseAgeExclude: + - '@wterm/core@0.3.0' + - '@wterm/dom@0.3.0' + - '@wterm/ghostty@0.3.0' blockExoticSubdeps: true strictDepBuilds: true trustPolicy: no-downgrade trustPolicyIgnoreAfter: 10080 trustPolicyExclude: [] -allowBuilds: {} +allowBuilds: + node-pty: true