diff --git a/.gitignore b/.gitignore index da1f1832..331632b3 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ docs/node_modules/ docs/.next/ docs/out/ docs/package-lock.json + +# pnpm +.pnpm-store/ diff --git a/README.md b/README.md index b82e79fb..317d6b76 100644 --- a/README.md +++ b/README.md @@ -691,6 +691,98 @@ Core workflow: ## Integrations +### iOS Simulator + +Control real Mobile Safari in the iOS Simulator for authentic mobile web testing. Requires macOS with Xcode. + +**Setup:** + +```bash +# Install Appium and XCUITest driver +npm install -g appium +appium driver install xcuitest +``` + +**Usage:** + +```bash +# List available iOS simulators +agent-browser device list + +# Launch Safari on a specific device +agent-browser -p ios --device "iPhone 16 Pro" open https://example.com + +# Same commands as desktop +agent-browser -p ios snapshot -i +agent-browser -p ios tap @e1 +agent-browser -p ios fill @e2 "text" +agent-browser -p ios screenshot mobile.png + +# Mobile-specific commands +agent-browser -p ios swipe up +agent-browser -p ios swipe down 500 + +# Close session +agent-browser -p ios close +``` + +Or use environment variables: + +```bash +export AGENT_BROWSER_PROVIDER=ios +export AGENT_BROWSER_IOS_DEVICE="iPhone 16 Pro" +agent-browser open https://example.com +``` + +| Variable | Description | +|----------|-------------| +| `AGENT_BROWSER_PROVIDER` | Set to `ios` to enable iOS mode | +| `AGENT_BROWSER_IOS_DEVICE` | Device name (e.g., "iPhone 16 Pro", "iPad Pro") | +| `AGENT_BROWSER_IOS_UDID` | Device UDID (alternative to device name) | + +**Supported devices:** All iOS Simulators available in Xcode (iPhones, iPads), plus real iOS devices. + +**Note:** The iOS provider boots the simulator, starts Appium, and controls Safari. First launch takes ~30-60 seconds; subsequent commands are fast. + +#### Real Device Support + +Appium also supports real iOS devices connected via USB. This requires additional one-time setup: + +**1. Get your device UDID:** +```bash +xcrun xctrace list devices +# or +system_profiler SPUSBDataType | grep -A 5 "iPhone\|iPad" +``` + +**2. Sign WebDriverAgent (one-time):** +```bash +# Open the WebDriverAgent Xcode project +cd ~/.appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent +open WebDriverAgent.xcodeproj +``` + +In Xcode: +- Select the `WebDriverAgentRunner` target +- Go to Signing & Capabilities +- Select your Team (requires Apple Developer account, free tier works) +- Let Xcode manage signing automatically + +**3. Use with agent-browser:** +```bash +# Connect device via USB, then: +agent-browser -p ios --device "" open https://example.com + +# Or use the device name if unique +agent-browser -p ios --device "John's iPhone" open https://example.com +``` + +**Real device notes:** +- First run installs WebDriverAgent to the device (may require Trust prompt) +- Device must be unlocked and connected via USB +- Slightly slower initial connection than simulator +- Tests against real Safari performance and behavior + ### Browserbase [Browserbase](https://browserbase.com) provides remote browser infrastructure to make deployment of agentic browsing agents easy. Use it when running the agent-browser CLI in an environment where a local browser isn't feasible. diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 26f466f5..50118abe 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -103,6 +103,12 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result Ok(json!({ "id": id, "action": "back" })), @@ -835,6 +841,48 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result { + // Alias for click (semantic clarity for touch interfaces) + let sel = rest.get(0).ok_or_else(|| ParseError::MissingArguments { + context: "tap".to_string(), + usage: "tap ", + })?; + Ok(json!({ "id": id, "action": "tap", "selector": sel })) + } + "swipe" => { + let direction = rest.get(0).ok_or_else(|| ParseError::MissingArguments { + context: "swipe".to_string(), + usage: "swipe [distance]", + })?; + let valid_directions = ["up", "down", "left", "right"]; + if !valid_directions.contains(direction) { + return Err(ParseError::InvalidValue { + message: format!("Invalid swipe direction: {}", direction), + usage: "swipe [distance]", + }); + } + let mut cmd = json!({ "id": id, "action": "swipe", "direction": direction }); + if let Some(distance) = rest.get(1) { + if let Ok(d) = distance.parse::() { + cmd.as_object_mut().unwrap().insert("distance".to_string(), json!(d)); + } + } + Ok(cmd) + } + "device" => { + match rest.get(0).map(|s| *s) { + Some("list") | None => { + // List available iOS simulators + Ok(json!({ "id": id, "action": "device_list" })) + } + Some(sub) => Err(ParseError::UnknownSubcommand { + subcommand: sub.to_string(), + valid_options: &["list"], + }), + } + } + _ => Err(ParseError::UnknownCommand { command: cmd.to_string(), }), @@ -1376,6 +1424,7 @@ mod tests { user_agent: None, provider: None, ignore_https_errors: false, + device: None, } } diff --git a/cli/src/connection.rs b/cli/src/connection.rs index 73710f37..d53efe02 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -215,6 +215,8 @@ pub fn ensure_daemon( ignore_https_errors: bool, profile: Option<&str>, state: Option<&str>, + provider: Option<&str>, + device: Option<&str>, ) -> Result { // Check if daemon is running AND responsive if is_daemon_running(session) && daemon_ready(session) { @@ -343,6 +345,14 @@ pub fn ensure_daemon( cmd.env("AGENT_BROWSER_STATE", st); } + if let Some(p) = provider { + cmd.env("AGENT_BROWSER_PROVIDER", p); + } + + if let Some(d) = device { + cmd.env("AGENT_BROWSER_IOS_DEVICE", d); + } + // Create new process group and session to fully detach unsafe { cmd.pre_exec(|| { @@ -410,6 +420,14 @@ pub fn ensure_daemon( cmd.env("AGENT_BROWSER_STATE", st); } + if let Some(p) = provider { + cmd.env("AGENT_BROWSER_PROVIDER", p); + } + + if let Some(d) = device { + cmd.env("AGENT_BROWSER_IOS_DEVICE", d); + } + // CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200; const DETACHED_PROCESS: u32 = 0x00000008; diff --git a/cli/src/flags.rs b/cli/src/flags.rs index 2abd1bc9..d97a275e 100644 --- a/cli/src/flags.rs +++ b/cli/src/flags.rs @@ -18,6 +18,7 @@ pub struct Flags { pub user_agent: Option, pub provider: Option, pub ignore_https_errors: bool, + pub device: Option, } pub fn parse_flags(args: &[String]) -> Flags { @@ -49,6 +50,7 @@ pub fn parse_flags(args: &[String]) -> Flags { user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(), provider: env::var("AGENT_BROWSER_PROVIDER").ok(), ignore_https_errors: false, + device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(), }; let mut i = 0; @@ -131,6 +133,12 @@ pub fn parse_flags(args: &[String]) -> Flags { } } "--ignore-https-errors" => flags.ignore_https_errors = true, + "--device" => { + if let Some(d) = args.get(i + 1) { + flags.device = Some(d.clone()); + i += 1; + } + } _ => {} } i += 1; @@ -165,6 +173,7 @@ pub fn clean_args(args: &[String]) -> Vec { "--user-agent", "-p", "--provider", + "--device", ]; for arg in args.iter() { diff --git a/cli/src/main.rs b/cli/src/main.rs index bd9138e5..a79d62c2 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -207,6 +207,8 @@ fn main() { flags.ignore_https_errors, flags.profile.as_deref(), flags.state.as_deref(), + flags.provider.as_deref(), + flags.device.as_deref(), ) { Ok(result) => result, Err(e) => { diff --git a/cli/src/output.rs b/cli/src/output.rs index 7388e2ae..b23fc888 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -78,6 +78,53 @@ pub fn print_response(resp: &Response, json_mode: bool, action: Option<&str>) { ); return; } + // iOS Devices + if let Some(devices) = data.get("devices").and_then(|v| v.as_array()) { + if devices.is_empty() { + println!("No iOS devices available. Open Xcode to download simulator runtimes."); + return; + } + + // Separate real devices from simulators + let real_devices: Vec<_> = devices + .iter() + .filter(|d| d.get("isRealDevice").and_then(|v| v.as_bool()).unwrap_or(false)) + .collect(); + let simulators: Vec<_> = devices + .iter() + .filter(|d| !d.get("isRealDevice").and_then(|v| v.as_bool()).unwrap_or(false)) + .collect(); + + if !real_devices.is_empty() { + println!("Connected Devices:\n"); + for device in real_devices.iter() { + let name = device.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown"); + let runtime = device.get("runtime").and_then(|v| v.as_str()).unwrap_or(""); + let udid = device.get("udid").and_then(|v| v.as_str()).unwrap_or(""); + println!(" {} {} ({})", color::green("●"), name, runtime); + println!(" {}", color::dim(udid)); + } + println!(); + } + + if !simulators.is_empty() { + println!("Simulators:\n"); + for device in simulators.iter() { + let name = device.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown"); + let runtime = device.get("runtime").and_then(|v| v.as_str()).unwrap_or(""); + let state = device.get("state").and_then(|v| v.as_str()).unwrap_or("Unknown"); + let udid = device.get("udid").and_then(|v| v.as_str()).unwrap_or(""); + let state_indicator = if state == "Booted" { + color::green("●") + } else { + color::dim("○") + }; + println!(" {} {} ({})", state_indicator, name, runtime); + println!(" {}", color::dim(udid)); + } + } + return; + } // Tabs if let Some(tabs) = data.get("tabs").and_then(|v| v.as_array()) { for (i, tab) in tabs.iter().enumerate() { @@ -1551,6 +1598,68 @@ Examples: "## } + // === iOS Commands === + "tap" => { + r##" +agent-browser tap - Tap an element (touch gesture) + +Usage: agent-browser tap + +Taps an element. This is an alias for 'click' that provides semantic clarity +for touch-based interfaces like iOS Safari. + +Options: + --json Output as JSON + --session Use specific session + +Examples: + agent-browser tap "#submit-button" + agent-browser tap @e1 + agent-browser -p ios tap "button:has-text('Sign In')" +"## + } + "swipe" => { + r##" +agent-browser swipe - Swipe gesture (iOS) + +Usage: agent-browser swipe [distance] + +Performs a swipe gesture on iOS Safari. The direction determines +which way the content moves (swipe up scrolls down, etc.). + +Arguments: + direction up, down, left, or right + distance Optional distance in pixels (default: 300) + +Options: + --json Output as JSON + --session Use specific session + +Examples: + agent-browser -p ios swipe up + agent-browser -p ios swipe down 500 + agent-browser -p ios swipe left +"## + } + "device" => { + r##" +agent-browser device - Manage iOS simulators + +Usage: agent-browser device + +Subcommands: + list List available iOS simulators + +Options: + --json Output as JSON + --session Use specific session + +Examples: + agent-browser device list + agent-browser -p ios device list +"## + } + _ => return false, }; println!("{}", help.trim()); @@ -1660,7 +1769,8 @@ Options: --proxy-bypass Bypass proxy for these hosts (or AGENT_BROWSER_PROXY_BYPASS) e.g., --proxy-bypass "localhost,*.internal.com" --ignore-https-errors Ignore HTTPS certificate errors - -p, --provider Cloud browser provider (or AGENT_BROWSER_PROVIDER env) + -p, --provider Browser provider: ios, browserbase, kernel, browseruse + --device iOS device name (e.g., "iPhone 15 Pro") --json JSON output --full, -f Full page screenshot --headed Show browser window (not headless) @@ -1671,8 +1781,10 @@ Options: Environment: AGENT_BROWSER_SESSION Session name (default: "default") AGENT_BROWSER_EXECUTABLE_PATH Custom browser executable path - AGENT_BROWSER_PROVIDER Cloud browser provider + AGENT_BROWSER_PROVIDER Browser provider (ios, browserbase, kernel, browseruse) AGENT_BROWSER_STREAM_PORT Enable WebSocket streaming on port (e.g., 9223) + AGENT_BROWSER_IOS_DEVICE Default iOS device name + AGENT_BROWSER_IOS_UDID Default iOS device UDID Examples: agent-browser open example.com @@ -1684,6 +1796,13 @@ Examples: agent-browser screenshot --full agent-browser --cdp 9222 snapshot # Connect via CDP port agent-browser --profile ~/.myapp open example.com # Persistent profile + +iOS Simulator (requires Xcode and Appium): + agent-browser -p ios open example.com # Use default iPhone + agent-browser -p ios --device "iPhone 15 Pro" open url # Specific device + agent-browser -p ios device list # List simulators + agent-browser -p ios swipe up # Swipe gesture + agent-browser -p ios tap @e1 # Touch element "# ); } diff --git a/docs/src/app/ios/page.tsx b/docs/src/app/ios/page.tsx new file mode 100644 index 00000000..896bdb17 --- /dev/null +++ b/docs/src/app/ios/page.tsx @@ -0,0 +1,280 @@ +import { CodeBlock } from "@/components/code-block"; + +export default function iOS() { + return ( +
+
+

iOS Simulator

+

+ Control real Mobile Safari in the iOS Simulator for authentic mobile + web testing. Uses Appium with XCUITest for native automation. +

+ +

Requirements

+
    +
  • macOS with Xcode installed
  • +
  • iOS Simulator runtimes (download via Xcode)
  • +
  • Appium with XCUITest driver
  • +
+ +

Setup

+ + +

List available devices

+

See all iOS simulators available on your system:

+ + +

Basic usage

+

+ Use the -p ios flag to enable iOS mode. The workflow is + identical to desktop: +

+ + +

Mobile-specific commands

+ + +

Environment variables

+

Configure iOS mode via environment variables:

+ + + + + + + + + + + + + + + + + + + + + + + +
VariableDescription
+ AGENT_BROWSER_PROVIDER + + Set to ios to enable iOS mode +
+ AGENT_BROWSER_IOS_DEVICE + Device name (e.g., "iPhone 16 Pro")
+ AGENT_BROWSER_IOS_UDID + Device UDID (alternative to device name)
+ +

Supported devices

+

+ All iOS Simulators available in Xcode are supported, including: +

+
    +
  • All iPhone models (iPhone 15, 16, 17, SE, etc.)
  • +
  • All iPad models (iPad Pro, iPad Air, iPad mini, etc.)
  • +
  • Multiple iOS versions (17.x, 18.x, etc.)
  • +
+

+ Real devices are also supported via USB connection + (see below). +

+ +

Real device support

+

+ Appium can control Safari on real iOS devices connected via USB. This + requires additional one-time setup. +

+ +

1. Get your device UDID

+ + +

2. Sign WebDriverAgent (one-time)

+

+ WebDriverAgent needs to be signed with your Apple Developer + certificate to run on real devices. +

+ +

In Xcode:

+
    +
  1. + Select the WebDriverAgentRunner target +
  2. +
  3. Go to Signing & Capabilities
  4. +
  5. + Select your Team (requires Apple Developer account, free tier works) +
  6. +
  7. Let Xcode manage signing automatically
  8. +
+ +

3. Use with agent-browser

+ " open https://example.com + +# Or use the device name if unique +agent-browser -p ios --device "John's iPhone" open https://example.com`} + /> + +

Real device notes

+
    +
  • + First run installs WebDriverAgent to the device (may require Trust + prompt on device) +
  • +
  • Device must be unlocked and connected via USB
  • +
  • Slightly slower initial connection than simulator
  • +
  • Tests against real Safari performance and behavior
  • +
  • + On first install, go to Settings → General → VPN & + Device Management to trust the developer certificate +
  • +
+ +

Performance notes

+
    +
  • + First launch: Takes 30-60 seconds to boot the + simulator and start Appium +
  • +
  • + Subsequent commands: Fast (simulator stays running) +
  • +
  • + Close command: Shuts down simulator and Appium + server +
  • +
+ +

Differences from desktop

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureDesktopiOS
BrowserChromium/Firefox/WebKitSafari only
TabsSupportedSingle tab only
PDF exportSupportedNot supported
ScreencastSupportedNot supported
Swipe gesturesNot nativeNative support
+ +

Troubleshooting

+

Appium not found

+ + +

No simulators available

+

+ Open Xcode and download iOS Simulator runtimes from{" "} + Settings → Platforms. +

+ +

Simulator won't boot

+

+ Try booting the simulator manually from Xcode or the Simulator app to + ensure it works, then retry with agent-browser. +

+
+
+ ); +} diff --git a/docs/src/components/sidebar.tsx b/docs/src/components/sidebar.tsx index 3adf3bb2..51b290e5 100644 --- a/docs/src/components/sidebar.tsx +++ b/docs/src/components/sidebar.tsx @@ -14,6 +14,7 @@ const navigation = [ { name: "Snapshots", href: "/snapshots" }, { name: "Streaming", href: "/streaming" }, { name: "CDP Mode", href: "/cdp-mode" }, + { name: "iOS Simulator", href: "/ios" }, { name: "Changelog", href: "/changelog" }, ]; diff --git a/package.json b/package.json index 04e937a2..2cf24bec 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,9 @@ }, "homepage": "https://github.com/vercel-labs/agent-browser#readme", "dependencies": { + "node-simctl": "^7.4.0", "playwright-core": "^1.57.0", + "webdriverio": "^9.15.0", "ws": "^8.19.0", "zod": "^3.22.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 571809e9..3488e40c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: dependencies: + node-simctl: + specifier: ^7.4.0 + version: 7.7.5 playwright-core: specifier: ^1.57.0 version: 1.57.0 + webdriverio: + specifier: ^9.15.0 + version: 9.23.3 ws: specifier: ^8.19.0 version: 8.19.0 @@ -47,10 +53,14 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.16 - version: 4.0.16(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.16(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) packages: + '@appium/logger@1.7.1': + resolution: {integrity: sha512-9C2o9X/lBEDBUnKfAi3mRo9oG7Z03nmISLwsGkWxIWjMAvBdJD0RRSJMekWVKzfXN3byrI1WlCXTITzN4LAoLw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=8'} + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -275,6 +285,10 @@ packages: '@types/node': optional: true + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -296,6 +310,18 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@promptbook/utils@0.69.5': + resolution: {integrity: sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==} + + '@puppeteer/browsers@2.11.2': + resolution: {integrity: sha512-GBY0+2lI9fDrjgb5dFL9+enKXqyOPok9PXg/69NVkjW3bikbK9RQrNrI3qccQXmDNN7ln4j/yL89Qgvj/tfqrw==} + engines: {node: '>=18'} + hasBin: true + '@rollup/rollup-android-arm-eabi@4.55.1': resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} cpu: [arm] @@ -424,6 +450,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -439,9 +468,18 @@ packages: '@types/node@20.19.28': resolution: {integrity: sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==} + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/which@2.0.2': + resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} @@ -471,6 +509,41 @@ packages: '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + '@wdio/config@9.23.3': + resolution: {integrity: sha512-tQCT1R6R3hdib7Qb+82Dxgn/sB+CiR8+GS4zyJh5vU0dzLGeYsCo2B5W89VLItvRjveTmsmh8NOQGV2KH0FHTQ==} + engines: {node: '>=18.20.0'} + + '@wdio/logger@9.18.0': + resolution: {integrity: sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==} + engines: {node: '>=18.20.0'} + + '@wdio/protocols@9.23.3': + resolution: {integrity: sha512-QfA3Gfl9/3QRX1FnH7x2+uZrgpkwYcksgk1bxGLzl/E0Qefp3BkhgHAfSB1+iKsiYIw9iFOLVx+x+zh0F4BSeg==} + + '@wdio/repl@9.16.2': + resolution: {integrity: sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==} + engines: {node: '>=18.20.0'} + + '@wdio/types@9.23.3': + resolution: {integrity: sha512-Ufjh06DAD7cGTMORUkq5MTZLw1nAgBSr2y8OyiNNuAfPGCwHEU3EwEfhG/y0V7S7xT5pBxliqWi7AjRrCgGcIA==} + engines: {node: '>=18.20.0'} + + '@wdio/utils@9.23.3': + resolution: {integrity: sha512-LO/cTpOcb3r49psjmWTxjFduHUMHDOhVfSzL1gfBCS5cGv6h3hAWOYw/94OrxLn1SIOgZu/hyLwf3SWeZB529g==} + engines: {node: '>=18.20.0'} + + '@zip.js/zip.js@2.8.16': + resolution: {integrity: sha512-kCjaXh50GCf9afcof6ekjXPKR//rBVIxNHJLSUaM3VAET2F0+hymgrK1GpInRIIFUpt+wsnUfgx2+bbrmc+7Tw==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -487,16 +560,32 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -505,18 +594,111 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asyncbox@3.0.0: + resolution: {integrity: sha512-X7U0nedUMKV3nn9c4R0Zgvdvv6cw97tbDlHSZicq1snGPi/oX9DgGmFSURWtxDdnBWd3V0YviKhqAYAVvoWQ/A==} + engines: {node: '>=16'} + + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.5.3: + resolution: {integrity: sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.3.2: + resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + basic-ftp@5.1.0: + resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} + engines: {node: '>=10.0.0'} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -524,6 +706,13 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.2.0: + resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} + engines: {node: '>=20.18.1'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -536,6 +725,17 @@ packages: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -543,10 +743,50 @@ packages: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-shorthand-properties@1.1.2: + resolution: {integrity: sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==} + + css-value@0.0.1: + resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -556,6 +796,18 @@ packages: supports-color: optional: true + decamelize@6.0.1: + resolution: {integrity: sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -564,13 +816,62 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + edge-paths@3.0.5: + resolution: {integrity: sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==} + engines: {node: '>=14.0.0'} + + edgedriver@6.3.0: + resolution: {integrity: sha512-ggEQL+oEyIcM4nP2QC3AtCQ04o4kDNefRM3hja0odvlPSnsaxiruMxEZ93v3gDCKWYW6BXUr51PPradb+3nffw==} + engines: {node: '>=20.0.0'} + hasBin: true + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding-sniffer@0.2.1: + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -583,17 +884,45 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -605,13 +934,31 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-xml-parser@5.3.4: + resolution: {integrity: sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -629,6 +976,10 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -647,10 +998,27 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + geckodriver@6.1.0: + resolution: {integrity: sha512-ZRXLa4ZaYTTgUO4Eefw+RsQCleugU2QLb1ME7qTYxxuRj51yAhfnXaItXNs5/vUzfIaDHuZ+YnSF005hfp07nQ==} + engines: {node: '>=20.0.0'} + hasBin: true + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -658,10 +1026,18 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -669,6 +1045,27 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + htmlfy@0.8.1: + resolution: {integrity: sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==} + + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -682,18 +1079,42 @@ packages: engines: {node: '>=18'} hasBin: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} @@ -710,6 +1131,14 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -722,9 +1151,23 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -736,6 +1179,16 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -749,17 +1202,46 @@ packages: resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} engines: {node: '>=18.0.0'} + locate-app@2.5.0: + resolution: {integrity: sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash.zip@4.2.0: + resolution: {integrity: sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + loglevel-plugin-prefix@0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -782,6 +1264,25 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + modern-tar@0.7.3: + resolution: {integrity: sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==} + engines: {node: '>=18.0.0'} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -794,13 +1295,31 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + + node-simctl@7.7.5: + resolution: {integrity: sha512-lWflzDW9xLuOOvR6mTJ9efbDtO/iSCH6rEGjxFxTV0vGgz5XjoZlW2BkNCCZib0B6Y23tCOiYhYJaMQYB8FKIQ==} + engines: {node: '>=14', npm: '>=8'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -832,9 +1351,32 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -847,6 +1389,10 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -854,6 +1400,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -898,9 +1447,33 @@ packages: engines: {node: '>=14'} hasBin: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -908,6 +1481,20 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -915,10 +1502,17 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resq@1.11.0: + resolution: {integrity: sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -926,6 +1520,13 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rgb2hex@0.2.5: + resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + rollup@4.55.1: resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -934,6 +1535,19 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safaridriver@1.0.1: + resolution: {integrity: sha512-jkg4434cYgtrIF2AeY/X0Wmd2W73cK5qIEFE3hDrrQenJH/2SDJIXGvPAigfvQTcE9+H31zkiNHbUqcihEiMRA==} + engines: {node: '>=18.0.0'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -942,6 +1556,16 @@ packages: engines: {node: '>=10'} hasBin: true + serialize-error@12.0.0: + resolution: {integrity: sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==} + engines: {node: '>=18'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -950,6 +1574,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -969,13 +1597,39 @@ packages: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spacetrim@0.11.59: + resolution: {integrity: sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -985,14 +1639,31 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1009,10 +1680,30 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strnum@2.1.2: + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + teen_process@2.3.3: + resolution: {integrity: sha512-NIdeetf/6gyEqLjnzvfgQe7PfipSceq2xDQM2Py2BkBnIIeWh3HRD3vNhulyO5WppfCv9z4mtsEHyq8kdiULTA==} + engines: {node: ^16.13.0 || >=18.0.0, npm: '>=8'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1032,11 +1723,22 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true + type-fest@4.26.0: + resolution: {integrity: sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==} + engines: {node: '>=16'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1045,12 +1747,34 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} + engines: {node: '>=18.17'} + + undici@7.20.0: + resolution: {integrity: sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==} + engines: {node: '>=20.18.1'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + + userhome@1.0.1: + resolution: {integrity: sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==} + engines: {node: '>= 0.8.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1123,20 +1847,68 @@ packages: jsdom: optional: true + wait-port@1.1.0: + resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} + engines: {node: '>=10'} + hasBin: true + + webdriver@9.23.3: + resolution: {integrity: sha512-8FdXOhzkxqDI6F1dyIsQONhKLDZ9HPSEwNBnH3bD1cHnj/6nVvyYrUtDPo/+J324BuwOa1IVTH3m8mb3B2hTlA==} + engines: {node: '>=18.20.0'} + + webdriverio@9.23.3: + resolution: {integrity: sha512-1dhMsBx/GLHJsDLhg/xuEQ48JZPrbldz7qdFT+MXQZADj9CJ4bJywWtVBME648MmVMfgDvLc5g2ThGIOupSLvQ==} + engines: {node: '>=18.20.0'} + peerDependencies: + puppeteer-core: '>=22.x || <=24.x' + peerDependenciesMeta: + puppeteer-core: + optional: true + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -1149,16 +1921,42 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: + '@appium/logger@1.7.1': + dependencies: + console-control-strings: 1.1.0 + lodash: 4.17.21 + lru-cache: 10.4.3 + set-blocking: 2.0.0 + '@babel/runtime@7.28.6': {} '@changesets/apply-release-plan@7.0.14': @@ -1390,6 +2188,15 @@ snapshots: optionalDependencies: '@types/node': 20.19.28 + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/sourcemap-codec@1.5.5': {} '@manypkg/find-root@1.1.0': @@ -1420,6 +2227,28 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@pkgjs/parseargs@0.11.0': + optional: true + + '@promptbook/utils@0.69.5': + dependencies: + spacetrim: 0.11.59 + + '@puppeteer/browsers@2.11.2': + dependencies: + debug: 4.4.3 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.3 + tar-fs: 3.1.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + '@rollup/rollup-android-arm-eabi@4.55.1': optional: true @@ -1497,6 +2326,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1512,10 +2343,19 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/which@2.0.2': {} + '@types/ws@8.18.1': dependencies: '@types/node': 20.19.28 + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.28 + optional: true + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.1.0 @@ -1525,13 +2365,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.16(vite@7.3.1(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@4.0.16': dependencies: @@ -1555,6 +2395,69 @@ snapshots: '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 + '@wdio/config@9.23.3': + dependencies: + '@wdio/logger': 9.18.0 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 + deepmerge-ts: 7.1.5 + glob: 10.5.0 + import-meta-resolve: 4.2.0 + jiti: 2.6.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@wdio/logger@9.18.0': + dependencies: + chalk: 5.6.2 + loglevel: 1.9.2 + loglevel-plugin-prefix: 0.8.4 + safe-regex2: 5.0.0 + strip-ansi: 7.1.2 + + '@wdio/protocols@9.23.3': {} + + '@wdio/repl@9.16.2': + dependencies: + '@types/node': 20.19.28 + + '@wdio/types@9.23.3': + dependencies: + '@types/node': 20.19.28 + + '@wdio/utils@9.23.3': + dependencies: + '@puppeteer/browsers': 2.11.2 + '@wdio/logger': 9.18.0 + '@wdio/types': 9.23.3 + decamelize: 6.0.1 + deepmerge-ts: 7.1.5 + edgedriver: 6.3.0 + geckodriver: 6.1.0 + get-port: 7.1.0 + import-meta-resolve: 4.2.0 + locate-app: 2.5.0 + mitt: 3.0.1 + safaridriver: 1.0.1 + split2: 4.2.0 + wait-port: 1.1.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@zip.js/zip.js@2.8.16': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + agent-base@7.1.4: {} + ansi-colors@4.1.3: {} ansi-escapes@7.2.0: @@ -1565,32 +2468,165 @@ snapshots: ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.23 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 argparse@2.0.1: {} + aria-query@5.3.2: {} + array-union@2.1.0: {} assertion-error@2.0.1: {} + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async@3.2.6: {} + + asyncbox@3.0.0: + dependencies: + bluebird: 3.7.2 + lodash: 4.17.23 + source-map-support: 0.5.21 + + b4a@1.7.3: {} + + balanced-match@1.0.2: {} + + bare-events@2.8.2: {} + + bare-fs@4.5.3: + dependencies: + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.8.2) + bare-url: 2.3.2 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true + + bare-stream@2.7.0(bare-events@2.8.2): + dependencies: + streamx: 2.23.0 + optionalDependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-url@2.3.2: + dependencies: + bare-path: 3.0.0 + optional: true + + base64-js@1.5.1: {} + + basic-ftp@5.1.0: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 + bluebird@3.7.2: {} + + boolbase@1.0.0: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 + buffer-crc32@0.2.13: {} + + buffer-crc32@1.0.0: {} + + buffer-from@1.1.2: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} chardet@2.1.1: {} + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.2.2 + css-what: 6.2.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.2.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 + htmlparser2: 10.1.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 7.20.0 + whatwg-mimetype: 4.0.0 + ci-info@3.9.0: {} cli-cursor@5.0.0: @@ -1602,33 +2638,149 @@ snapshots: slice-ansi: 5.0.0 string-width: 7.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + colorette@2.0.20: {} commander@13.1.0: {} + commander@9.5.0: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + console-control-strings@1.1.0: {} + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-shorthand-properties@1.1.2: {} + + css-value@0.0.1: {} + + css-what@6.2.2: {} + + data-uri-to-buffer@6.0.2: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decamelize@6.0.1: {} + + deepmerge-ts@7.1.5: {} + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + detect-indent@6.1.0: {} dir-glob@3.0.1: dependencies: path-type: 4.0.0 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + eastasianwidth@0.2.0: {} + + edge-paths@3.0.5: + dependencies: + '@types/which': 2.0.2 + which: 2.0.2 + + edgedriver@6.3.0: + dependencies: + '@wdio/logger': 9.18.0 + '@zip.js/zip.js': 2.8.16 + decamelize: 6.0.1 + edge-paths: 3.0.5 + fast-xml-parser: 5.3.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + which: 6.0.0 + transitivePeerDependencies: + - supports-color + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding-sniffer@0.2.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + + entities@6.0.1: {} + + entities@7.0.1: {} + environment@1.1.0: {} es-module-lexer@1.7.0: {} @@ -1662,14 +2814,38 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + esprima@4.0.1: {} + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + eventemitter3@5.0.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -1686,6 +2862,20 @@ snapshots: extendable-error@0.1.7: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@2.0.1: {} + + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1694,10 +2884,18 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-xml-parser@5.3.4: + dependencies: + strnum: 2.1.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -1711,6 +2909,11 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -1729,18 +2932,54 @@ snapshots: fsevents@2.3.3: optional: true + geckodriver@6.1.0: + dependencies: + '@wdio/logger': 9.18.0 + '@zip.js/zip.js': 2.8.16 + decamelize: 6.0.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + modern-tar: 0.7.3 + transitivePeerDependencies: + - supports-color + + get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-port@7.1.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + get-stream@8.0.1: {} get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 + get-uri@6.0.5: + dependencies: + basic-ftp: 5.1.0 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -1752,20 +2991,63 @@ snapshots: graceful-fs@4.2.11: {} + grapheme-splitter@1.0.4: {} + + has-flag@4.0.0: {} + + htmlfy@0.8.1: {} + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-id@4.1.3: {} human-signals@5.0.0: {} husky@9.1.7: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} + immediate@3.0.6: {} + + import-meta-resolve@4.2.0: {} + + inherits@2.0.4: {} + + ip-address@10.1.0: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@4.0.0: {} is-fullwidth-code-point@5.1.0: @@ -1778,6 +3060,10 @@ snapshots: is-number@7.0.0: {} + is-plain-obj@4.1.0: {} + + is-stream@2.0.1: {} + is-stream@3.0.0: {} is-subdir@1.2.0: @@ -1786,8 +3072,20 @@ snapshots: is-windows@1.0.2: {} + isarray@1.0.0: {} + isexe@2.0.0: {} + isexe@3.1.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@2.6.1: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -1801,6 +3099,21 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lint-staged@15.5.2: @@ -1827,12 +3140,26 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 + locate-app@2.5.0: + dependencies: + '@promptbook/utils': 0.69.5 + type-fest: 4.26.0 + userhome: 1.0.1 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 + lodash.clonedeep@4.5.0: {} + lodash.startcase@4.4.0: {} + lodash.zip@4.2.0: {} + + lodash@4.17.21: {} + + lodash@4.17.23: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.2.0 @@ -1841,6 +3168,14 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 9.0.2 + loglevel-plugin-prefix@0.8.4: {} + + loglevel@1.9.2: {} + + lru-cache@10.4.3: {} + + lru-cache@7.18.3: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1858,18 +3193,57 @@ snapshots: mimic-function@5.0.1: {} + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + mitt@3.0.1: {} + + modern-tar@0.7.3: {} + mri@1.2.0: {} ms@2.1.3: {} nanoid@3.3.11: {} + netmask@2.0.2: {} + + node-simctl@7.7.5: + dependencies: + '@appium/logger': 1.7.1 + asyncbox: 3.0.0 + bluebird: 3.7.2 + lodash: 4.17.23 + rimraf: 5.0.10 + semver: 7.7.3 + source-map-support: 0.5.21 + teen_process: 2.3.3 + uuid: 11.1.0 + which: 5.0.0 + + normalize-path@3.0.0: {} + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + obug@2.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -1896,20 +3270,62 @@ snapshots: p-try@2.2.0: {} + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + + package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.11: dependencies: quansync: 0.2.11 + pako@1.0.11: {} + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.3.0 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.3.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} path-key@4.0.0: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-type@4.0.0: {} pathe@2.0.3: {} + pend@1.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -1938,8 +3354,36 @@ snapshots: prettier@3.7.4: {} + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + progress@2.0.3: {} + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + quansync@0.2.11: {} + query-selector-shadow-dom@1.0.1: {} + queue-microtask@1.2.3: {} read-yaml-file@1.1.0: @@ -1949,19 +3393,55 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + require-directory@2.1.1: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} + resq@1.11.0: + dependencies: + fast-deep-equal: 2.0.1 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 signal-exit: 4.1.0 + ret@0.5.0: {} + reusify@1.1.0: {} rfdc@1.4.1: {} + rgb2hex@0.2.5: {} + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 @@ -1997,16 +3477,36 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safaridriver@1.0.1: {} + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex2@5.0.0: + dependencies: + ret: 0.5.0 + safer-buffer@2.1.2: {} semver@7.7.3: {} + serialize-error@12.0.0: + dependencies: + type-fest: 4.41.0 + + set-blocking@2.0.0: {} + + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -2023,27 +3523,82 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + spacetrim@0.11.59: {} + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + split2@4.2.0: {} + sprintf-js@1.0.3: {} stackback@0.0.2: {} std-env@3.10.0: {} + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + string-argv@0.3.2: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2056,8 +3611,48 @@ snapshots: strip-final-newline@3.0.0: {} + strnum@2.1.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tar-fs@3.1.1: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.5.3 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + teen_process@2.3.3: + dependencies: + bluebird: 3.7.2 + lodash: 4.17.23 + shell-quote: 1.8.3 + source-map-support: 0.5.21 + term-size@2.2.1: {} + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -2073,6 +3668,8 @@ snapshots: dependencies: is-number: 7.0.0 + tslib@2.8.1: {} + tsx@4.21.0: dependencies: esbuild: 0.27.2 @@ -2080,13 +3677,29 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-fest@4.26.0: {} + + type-fest@4.41.0: {} + typescript@5.9.3: {} undici-types@6.21.0: {} + undici@6.23.0: {} + + undici@7.20.0: {} + universalify@0.1.2: {} - vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2): + urlpattern-polyfill@10.1.0: {} + + userhome@1.0.1: {} + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + vite@7.3.1(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -2097,13 +3710,14 @@ snapshots: optionalDependencies: '@types/node': 20.19.28 fsevents: 2.3.3 + jiti: 2.6.1 tsx: 4.21.0 yaml: 2.8.2 - vitest@4.0.16(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.16(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -2120,7 +3734,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.28)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.28 @@ -2137,23 +3751,140 @@ snapshots: - tsx - yaml + wait-port@1.1.0: + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + webdriver@9.23.3: + dependencies: + '@types/node': 20.19.28 + '@types/ws': 8.18.1 + '@wdio/config': 9.23.3 + '@wdio/logger': 9.18.0 + '@wdio/protocols': 9.23.3 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 + deepmerge-ts: 7.1.5 + https-proxy-agent: 7.0.6 + undici: 6.23.0 + ws: 8.19.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + webdriverio@9.23.3: + dependencies: + '@types/node': 20.19.28 + '@types/sinonjs__fake-timers': 8.1.5 + '@wdio/config': 9.23.3 + '@wdio/logger': 9.18.0 + '@wdio/protocols': 9.23.3 + '@wdio/repl': 9.16.2 + '@wdio/types': 9.23.3 + '@wdio/utils': 9.23.3 + archiver: 7.0.1 + aria-query: 5.3.2 + cheerio: 1.2.0 + css-shorthand-properties: 1.1.2 + css-value: 0.0.1 + grapheme-splitter: 1.0.4 + htmlfy: 0.8.1 + is-plain-obj: 4.1.0 + jszip: 3.10.1 + lodash.clonedeep: 4.5.0 + lodash.zip: 4.2.0 + query-selector-shadow-dom: 1.0.1 + resq: 1.11.0 + rgb2hex: 0.2.5 + serialize-error: 12.0.0 + urlpattern-polyfill: 10.1.0 + webdriver: 9.23.3 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.1 + + which@6.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + ws@8.19.0: {} + y18n@5.0.8: {} + yaml@2.8.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + zod@3.25.76: {} diff --git a/skills/agent-browser/SKILL.md b/skills/agent-browser/SKILL.md index b2f9b6ec..f07d3f70 100644 --- a/skills/agent-browser/SKILL.md +++ b/skills/agent-browser/SKILL.md @@ -129,6 +129,32 @@ agent-browser highlight @e1 # Highlight element agent-browser record start demo.webm # Record session ``` +### iOS Simulator (Mobile Safari) + +```bash +# List available iOS simulators +agent-browser device list + +# Launch Safari on a specific device +agent-browser -p ios --device "iPhone 16 Pro" open https://example.com + +# Same workflow as desktop - snapshot, interact, re-snapshot +agent-browser -p ios snapshot -i +agent-browser -p ios tap @e1 # Tap (alias for click) +agent-browser -p ios fill @e2 "text" +agent-browser -p ios swipe up # Mobile-specific gesture + +# Take screenshot +agent-browser -p ios screenshot mobile.png + +# Close session (shuts down simulator) +agent-browser -p ios close +``` + +**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`) + +**Real devices:** Works with physical iOS devices if pre-configured. Use `--device ""` where UDID is from `xcrun xctrace list devices`. + ## Ref Lifecycle (Important) Refs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after: diff --git a/src/daemon.ts b/src/daemon.ts index 7c32eca1..ee4545f9 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -3,10 +3,15 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { BrowserManager } from './browser.js'; +import { IOSManager } from './ios-manager.js'; import { parseCommand, serializeResponse, errorResponse } from './protocol.js'; import { executeCommand } from './actions.js'; +import { executeIOSCommand } from './ios-actions.js'; import { StreamServer } from './stream-server.js'; +// Manager type - either desktop browser or iOS +type Manager = BrowserManager | IOSManager; + // Platform detection const isWindows = process.platform === 'win32'; @@ -167,8 +172,12 @@ export function getStreamPortFile(session?: string): string { /** * Start the daemon server * @param options.streamPort Port for WebSocket stream server (0 to disable) + * @param options.provider Provider type ('ios' for iOS Simulator, undefined for desktop) */ -export async function startDaemon(options?: { streamPort?: number }): Promise { +export async function startDaemon(options?: { + streamPort?: number; + provider?: string; +}): Promise { // Ensure socket directory exists const socketDir = getSocketDir(); if (!fs.existsSync(socketDir)) { @@ -178,18 +187,24 @@ export async function startDaemon(options?: { streamPort?: number }): Promise 0) { - streamServer = new StreamServer(browser, streamPort); + if (streamPort > 0 && !isIOS && manager instanceof BrowserManager) { + streamServer = new StreamServer(manager, streamPort); await streamServer.start(); // Write stream port to file for clients to discover @@ -233,56 +248,91 @@ export async function startDaemon(options?: { streamPort?: number }): Promise p.trim()) - .filter(Boolean) - : undefined; - - // Parse args from env (comma or newline separated) - const argsEnv = process.env.AGENT_BROWSER_ARGS; - const args = argsEnv - ? argsEnv - .split(/[,\n]/) - .map((a) => a.trim()) - .filter((a) => a.length > 0) - : undefined; - - // Parse proxy from env - const proxyServer = process.env.AGENT_BROWSER_PROXY; - const proxyBypass = process.env.AGENT_BROWSER_PROXY_BYPASS; - const proxy = proxyServer - ? { - server: proxyServer, - ...(proxyBypass && { bypass: proxyBypass }), - } - : undefined; - - const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1'; - await browser.launch({ - id: 'auto', - action: 'launch' as const, - headless: process.env.AGENT_BROWSER_HEADED !== '1', - executablePath: process.env.AGENT_BROWSER_EXECUTABLE_PATH, - extensions: extensions, - profile: process.env.AGENT_BROWSER_PROFILE, - storageState: process.env.AGENT_BROWSER_STATE, - args, - userAgent: process.env.AGENT_BROWSER_USER_AGENT, - proxy, - ignoreHTTPSErrors: ignoreHTTPSErrors, - }); + if (isIOS && manager instanceof IOSManager) { + // Auto-launch iOS Safari + // Check for device in command first (for reused daemons), then fall back to env vars + const cmd = parseResult.command as { iosDevice?: string }; + const iosDevice = cmd.iosDevice || process.env.AGENT_BROWSER_IOS_DEVICE; + await manager.launch({ + device: iosDevice, + udid: process.env.AGENT_BROWSER_IOS_UDID, + }); + } else if (manager instanceof BrowserManager) { + // Auto-launch desktop browser + const extensions = process.env.AGENT_BROWSER_EXTENSIONS + ? process.env.AGENT_BROWSER_EXTENSIONS.split(',') + .map((p) => p.trim()) + .filter(Boolean) + : undefined; + + // Parse args from env (comma or newline separated) + const argsEnv = process.env.AGENT_BROWSER_ARGS; + const args = argsEnv + ? argsEnv + .split(/[,\n]/) + .map((a) => a.trim()) + .filter((a) => a.length > 0) + : undefined; + + // Parse proxy from env + const proxyServer = process.env.AGENT_BROWSER_PROXY; + const proxyBypass = process.env.AGENT_BROWSER_PROXY_BYPASS; + const proxy = proxyServer + ? { + server: proxyServer, + ...(proxyBypass && { bypass: proxyBypass }), + } + : undefined; + + const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1'; + await manager.launch({ + id: 'auto', + action: 'launch' as const, + headless: process.env.AGENT_BROWSER_HEADED !== '1', + executablePath: process.env.AGENT_BROWSER_EXECUTABLE_PATH, + extensions: extensions, + profile: process.env.AGENT_BROWSER_PROFILE, + storageState: process.env.AGENT_BROWSER_STATE, + args, + userAgent: process.env.AGENT_BROWSER_USER_AGENT, + proxy, + ignoreHTTPSErrors: ignoreHTTPSErrors, + }); + } } - // Handle close command specially + // Handle close command specially - shuts down daemon if (parseResult.command.action === 'close') { - const response = await executeCommand(parseResult.command, browser); + const response = + isIOS && manager instanceof IOSManager + ? await executeIOSCommand(parseResult.command, manager) + : await executeCommand(parseResult.command, manager as BrowserManager); socket.write(serializeResponse(response) + '\n'); if (!shuttingDown) { @@ -296,7 +346,11 @@ export async function startDaemon(options?: { streamPort?: number }): Promise(id: string, data: T): Response { + return { id, success: true, data }; +} + +function errorResponse(id: string, error: string): Response { + return { id, success: false, error }; +} + +/** + * Execute a command on the iOS manager + */ +export async function executeIOSCommand(command: Command, manager: IOSManager): Promise { + const { id, action } = command; + + try { + switch (action) { + case 'launch': { + const cmd = command as any; + await manager.launch({ + device: cmd.device, + udid: cmd.udid, + }); + const info = manager.getDeviceInfo(); + return successResponse(id, { + launched: true, + device: info?.name ?? 'iOS Simulator', + udid: info?.udid, + }); + } + + case 'navigate': { + const cmd = command as any; + const result = await manager.navigate(cmd.url); + return successResponse(id, result); + } + + case 'click': { + const cmd = command as any; + await manager.click(cmd.selector); + return successResponse(id, { clicked: true }); + } + + case 'tap': { + const cmd = command as any; + await manager.tap(cmd.selector); + return successResponse(id, { tapped: true }); + } + + case 'type': { + const cmd = command as any; + await manager.type(cmd.selector, cmd.text, { + delay: cmd.delay, + clear: cmd.clear, + }); + return successResponse(id, { typed: true }); + } + + case 'fill': { + const cmd = command as any; + await manager.fill(cmd.selector, cmd.value); + return successResponse(id, { filled: true }); + } + + case 'screenshot': { + const cmd = command as any; + const result = await manager.screenshot({ + path: cmd.path, + fullPage: cmd.fullPage, + }); + return successResponse(id, result); + } + + case 'snapshot': { + const cmd = command as any; + const result = await manager.getSnapshot({ + interactive: cmd.interactive, + }); + return successResponse(id, { snapshot: result.tree, refs: result.refs }); + } + + case 'scroll': { + const cmd = command as any; + await manager.scroll({ + selector: cmd.selector, + x: cmd.x, + y: cmd.y, + direction: cmd.direction, + amount: cmd.amount, + }); + return successResponse(id, { scrolled: true }); + } + + case 'swipe': { + const cmd = command as any; + await manager.swipe(cmd.direction, { distance: cmd.distance }); + return successResponse(id, { swiped: true }); + } + + case 'evaluate': { + const cmd = command as any; + const result = await manager.evaluate(cmd.script, ...(cmd.args ?? [])); + return successResponse(id, { result }); + } + + case 'wait': { + const cmd = command as any; + await manager.wait({ + selector: cmd.selector, + timeout: cmd.timeout, + state: cmd.state, + }); + return successResponse(id, { waited: true }); + } + + case 'press': { + const cmd = command as any; + await manager.press(cmd.key); + return successResponse(id, { pressed: true }); + } + + case 'hover': { + const cmd = command as any; + await manager.hover(cmd.selector); + return successResponse(id, { hovered: true }); + } + + case 'content': { + const cmd = command as any; + const html = await manager.getContent(cmd.selector); + return successResponse(id, { html }); + } + + case 'gettext': { + const cmd = command as any; + const text = await manager.getText(cmd.selector); + return successResponse(id, { text }); + } + + case 'getattribute': { + const cmd = command as any; + const value = await manager.getAttribute(cmd.selector, cmd.attribute); + return successResponse(id, { value }); + } + + case 'isvisible': { + const cmd = command as any; + const visible = await manager.isVisible(cmd.selector); + return successResponse(id, { visible }); + } + + case 'isenabled': { + const cmd = command as any; + const enabled = await manager.isEnabled(cmd.selector); + return successResponse(id, { enabled }); + } + + case 'url': { + const url = await manager.getUrl(); + return successResponse(id, { url }); + } + + case 'title': { + const title = await manager.getTitle(); + return successResponse(id, { title }); + } + + case 'back': { + await manager.goBack(); + return successResponse(id, { navigated: 'back' }); + } + + case 'forward': { + await manager.goForward(); + return successResponse(id, { navigated: 'forward' }); + } + + case 'reload': { + await manager.reload(); + return successResponse(id, { reloaded: true }); + } + + case 'select': { + const cmd = command as any; + await manager.select(cmd.selector, cmd.values); + return successResponse(id, { selected: true }); + } + + case 'check': { + const cmd = command as any; + await manager.check(cmd.selector); + return successResponse(id, { checked: true }); + } + + case 'uncheck': { + const cmd = command as any; + await manager.uncheck(cmd.selector); + return successResponse(id, { unchecked: true }); + } + + case 'focus': { + const cmd = command as any; + await manager.focus(cmd.selector); + return successResponse(id, { focused: true }); + } + + case 'clear': { + const cmd = command as any; + await manager.clear(cmd.selector); + return successResponse(id, { cleared: true }); + } + + case 'count': { + const cmd = command as any; + const count = await manager.count(cmd.selector); + return successResponse(id, { count }); + } + + case 'boundingbox': { + const cmd = command as any; + const box = await manager.getBoundingBox(cmd.selector); + return successResponse(id, { box }); + } + + case 'close': { + await manager.close(); + return successResponse(id, { closed: true }); + } + + // iOS-specific: device list + case 'device_list': { + const devices = await manager.listDevices(); + return successResponse(id, { devices }); + } + + // Commands that don't apply to iOS Safari + case 'tab_new': + case 'tab_list': + case 'tab_switch': + case 'tab_close': + case 'window_new': + return errorResponse( + id, + `Command '${action}' is not supported on iOS Safari. Mobile Safari does not support programmatic tab management.` + ); + + case 'pdf': + return errorResponse(id, 'PDF generation is not supported on iOS Safari.'); + + case 'screencast_start': + case 'screencast_stop': + return errorResponse(id, 'Screencast is not supported on iOS (requires CDP).'); + + case 'recording_start': + case 'recording_stop': + case 'recording_restart': + return errorResponse(id, 'Video recording is not yet supported on iOS.'); + + default: + return errorResponse(id, `Unknown or unsupported iOS command: ${action}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return errorResponse(id, message); + } +} diff --git a/src/ios-manager.test.ts b/src/ios-manager.test.ts new file mode 100644 index 00000000..a7851cff --- /dev/null +++ b/src/ios-manager.test.ts @@ -0,0 +1,157 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { IOSManager } from './ios-manager.js'; + +// Mock node-simctl +vi.mock('node-simctl', () => { + return { + Simctl: class MockSimctl { + async getDevices() { + return { + 'iOS 18.0': [ + { + name: 'iPhone 16 Pro', + udid: 'TEST-UDID-1234', + state: 'Shutdown', + isAvailable: true, + }, + { + name: 'iPhone 16', + udid: 'TEST-UDID-5678', + state: 'Booted', + isAvailable: true, + }, + { + name: 'iPad Pro', + udid: 'TEST-UDID-IPAD', + state: 'Shutdown', + isAvailable: true, + }, + ], + }; + } + }, + }; +}); + +describe('IOSManager', () => { + let manager: IOSManager; + + beforeEach(() => { + manager = new IOSManager(); + }); + + describe('listDevices', () => { + it('should list available iOS simulators', async () => { + const devices = await manager.listDevices(); + + expect(devices).toHaveLength(3); + expect(devices[0]).toEqual({ + name: 'iPhone 16 Pro', + udid: 'TEST-UDID-1234', + state: 'Shutdown', + runtime: 'iOS 18.0', + isAvailable: true, + isRealDevice: false, + }); + }); + + it('should include runtime version for each device', async () => { + const devices = await manager.listDevices(); + + devices.forEach((device) => { + expect(device.runtime).toBe('iOS 18.0'); + }); + }); + }); + + describe('isLaunched', () => { + it('should return false when browser is not launched', () => { + expect(manager.isLaunched()).toBe(false); + }); + }); + + describe('getRefData', () => { + it('should return null for unknown refs', () => { + // Access private method via bracket notation for testing + const result = (manager as any).getRefData('@e99'); + expect(result).toBeNull(); + }); + + it('should handle @-prefixed refs', () => { + // Set up a ref in the refMap + (manager as any).refMap = { + e1: { selector: 'button', role: 'button', name: 'Submit' }, + }; + + const result = (manager as any).getRefData('@e1'); + expect(result).toEqual({ selector: 'button', role: 'button', name: 'Submit' }); + }); + + it('should handle ref= prefixed refs', () => { + (manager as any).refMap = { + e2: { selector: 'a', role: 'link', name: 'Learn more' }, + }; + + const result = (manager as any).getRefData('ref=e2'); + expect(result).toEqual({ selector: 'a', role: 'link', name: 'Learn more' }); + }); + + it('should handle bare ref names', () => { + (manager as any).refMap = { + e3: { selector: 'input', role: 'textbox', name: 'Email' }, + }; + + const result = (manager as any).getRefData('e3'); + expect(result).toEqual({ selector: 'input', role: 'textbox', name: 'Email' }); + }); + }); +}); + +describe('IOSManager integration', () => { + // These tests require Appium and iOS Simulator to be available + // They are skipped by default and can be run manually + describe.skip('with real simulator', () => { + let manager: IOSManager; + + beforeEach(() => { + // Use real implementation for integration tests + vi.resetModules(); + manager = new IOSManager(); + }); + + it('should launch Safari and navigate', async () => { + await manager.launch({ device: 'iPhone 16 Pro' }); + expect(manager.isLaunched()).toBe(true); + + const result = await manager.navigate('https://example.com'); + expect(result.url).toContain('example.com'); + expect(result.title).toBe('Example Domain'); + + await manager.close(); + }, 120000); + + it('should take screenshots', async () => { + await manager.launch({ device: 'iPhone 16 Pro' }); + await manager.navigate('https://example.com'); + + const result = await manager.screenshot(); + expect(result.base64).toBeDefined(); + expect(result.base64?.length).toBeGreaterThan(1000); + + await manager.close(); + }, 120000); + + it('should generate snapshots with refs', async () => { + await manager.launch({ device: 'iPhone 16 Pro' }); + await manager.navigate('https://example.com'); + + const snapshot = await manager.getSnapshot(); + expect(snapshot.tree).toContain('link'); + expect(snapshot.tree).toContain('[ref=e1]'); + expect(snapshot.refs.e1).toBeDefined(); + expect(snapshot.refs.e1.role).toBe('link'); + + await manager.close(); + }, 120000); + }); +}); diff --git a/src/ios-manager.ts b/src/ios-manager.ts new file mode 100644 index 00000000..825d1e97 --- /dev/null +++ b/src/ios-manager.ts @@ -0,0 +1,1299 @@ +/** + * iOS Simulator Manager - Manages iOS Simulator and Safari automation via Appium. + * + * This provides 1:1 command parity with BrowserManager for iOS Safari. + */ + +// Declare browser globals used in execute() callbacks - these run in browser context, not Node +declare const document: any; +declare const window: any; + +import { Simctl } from 'node-simctl'; +import { remote, type Browser as WDIOBrowser } from 'webdriverio'; +import { spawn, type ChildProcess } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; + +// Ref map for element targeting (mirrors snapshot.ts) +export interface IOSRefMap { + [ref: string]: { + selector: string; + role?: string; + name?: string; + xpath?: string; + }; +} + +export interface IOSEnhancedSnapshot { + tree: string; + refs: IOSRefMap; +} + +interface ConsoleMessage { + type: string; + text: string; + timestamp: number; +} + +interface IOSDeviceInfo { + name: string; + udid: string; + state: string; + runtime: string; + isAvailable: boolean; + isRealDevice?: boolean; +} + +/** + * Manages iOS Simulator and Safari automation via Appium + */ +export class IOSManager { + private simctl: Simctl; + private browser: WDIOBrowser | null = null; + private appiumProcess: ChildProcess | null = null; + private deviceUdid: string | null = null; + private deviceName: string | null = null; + private consoleMessages: ConsoleMessage[] = []; + private refMap: IOSRefMap = {}; + private lastSnapshot: string = ''; + private refCounter: number = 0; + + // Default Appium port + private static readonly APPIUM_PORT = 4723; + private static readonly APPIUM_HOST = '127.0.0.1'; + + constructor() { + this.simctl = new Simctl(); + } + + /** + * Check if browser is launched + */ + isLaunched(): boolean { + return this.browser !== null; + } + + /** + * List connected real iOS devices + */ + private async listRealDevices(): Promise { + const devices: IOSDeviceInfo[] = []; + + try { + // Use xcrun xctrace to list connected devices + const { execSync } = await import('node:child_process'); + const output = execSync('xcrun xctrace list devices 2>/dev/null || true', { + encoding: 'utf-8', + timeout: 10000, + }); + + // Parse output - format is: + // == Devices == + // Device Name (OS Version) (UDID) + // Real devices show version as just "26.2", simulators as "iOS 18.0" + const lines = output.split('\n'); + let inDevicesSection = false; + + for (const line of lines) { + if (line.includes('== Devices ==')) { + inDevicesSection = true; + continue; + } + // Stop at Simulators or Devices Offline section + if (line.includes('== Simulators ==') || line.includes('== Devices Offline ==')) { + break; + } + + if (inDevicesSection && line.trim()) { + // Match pattern: "Device Name (version) (UDID)" + const match = line.match(/^(.+?)\s+\(([^)]+)\)\s+\(([A-F0-9-]+)\)$/i); + if (match) { + const [, name, version, udid] = match; + const nameLower = name.toLowerCase(); + // Include iOS devices: either name contains iPhone/iPad, or version looks like iOS + // (a simple version number like "26.2" or "18.6") and isn't a Mac + const isIOS = + nameLower.includes('iphone') || + nameLower.includes('ipad') || + version.includes('iOS') || + version.includes('iPadOS'); + const isMac = + nameLower.includes('mac') || + nameLower.includes('macbook') || + nameLower.includes('imac'); + + if (isIOS || (!isMac && /^\d+\.\d+(\.\d+)?$/.test(version))) { + devices.push({ + name: name.trim(), + udid: udid, + state: 'Connected', + runtime: `iOS ${version}`, + isAvailable: true, + isRealDevice: true, + }); + } + } + } + } + } catch { + // Ignore errors - real device listing is optional + } + + return devices; + } + + /** + * List available iOS simulators + */ + async listDevices(): Promise { + const devices: IOSDeviceInfo[] = []; + + try { + const rawDevices = await this.simctl.getDevices(); + + for (const [runtime, deviceList] of Object.entries(rawDevices)) { + if (!Array.isArray(deviceList)) continue; + + for (const device of deviceList) { + // Only include iPhone and iPad simulators + if (device.name && (device.name.includes('iPhone') || device.name.includes('iPad'))) { + devices.push({ + name: device.name, + udid: device.udid, + state: device.state, + runtime: runtime, + isAvailable: device.isAvailable ?? true, + isRealDevice: false, + }); + } + } + } + } catch (error) { + throw new Error( + `Failed to list iOS simulators. Is Xcode installed? Error: ${error instanceof Error ? error.message : String(error)}` + ); + } + + return devices; + } + + /** + * List all devices (simulators + real devices) + */ + async listAllDevices(): Promise { + const [simulators, realDevices] = await Promise.all([ + this.listDevices(), + this.listRealDevices(), + ]); + + // Real devices first, then simulators + return [...realDevices, ...simulators]; + } + + /** + * Find the best default device (most recent iPhone) + */ + private async findDefaultDevice(): Promise { + const devices = await this.listDevices(); + + // Filter to available iPhones, prefer Pro models, then by name (which typically indicates recency) + const iphones = devices + .filter((d) => d.isAvailable && d.name.includes('iPhone')) + .sort((a, b) => { + // Prefer Pro models + const aIsPro = a.name.includes('Pro') ? 1 : 0; + const bIsPro = b.name.includes('Pro') ? 1 : 0; + if (aIsPro !== bIsPro) return bIsPro - aIsPro; + + // Then sort by name descending (iPhone 15 > iPhone 14) + return b.name.localeCompare(a.name); + }); + + return iphones[0] ?? null; + } + + /** + * Find device by name or UDID (searches both simulators and real devices) + */ + private async findDevice(nameOrUdid: string): Promise { + const devices = await this.listAllDevices(); + + // Try exact UDID match first + const byUdid = devices.find((d) => d.udid === nameOrUdid); + if (byUdid) return byUdid; + + // Try exact name match + const byExactName = devices.find((d) => d.name === nameOrUdid); + if (byExactName) return byExactName; + + // Try partial name match + const byPartialName = devices.find((d) => + d.name.toLowerCase().includes(nameOrUdid.toLowerCase()) + ); + return byPartialName ?? null; + } + + /** + * Check if Appium is installed + */ + private async checkAppiumInstalled(): Promise { + return new Promise((resolve) => { + const proc = spawn('appium', ['--version'], { shell: true }); + proc.on('close', (code) => resolve(code === 0)); + proc.on('error', () => resolve(false)); + }); + } + + /** + * Check if Appium server is already running + */ + private async isAppiumRunning(): Promise { + try { + const response = await fetch( + `http://${IOSManager.APPIUM_HOST}:${IOSManager.APPIUM_PORT}/status` + ); + return response.ok; + } catch { + return false; + } + } + + /** + * Start Appium server if not already running + */ + private async startAppiumServer(): Promise { + if (await this.isAppiumRunning()) { + return; // Already running + } + + if (!(await this.checkAppiumInstalled())) { + throw new Error( + 'Appium not installed. Run: npm install -g appium && appium driver install xcuitest' + ); + } + + return new Promise((resolve, reject) => { + this.appiumProcess = spawn( + 'appium', + ['--port', String(IOSManager.APPIUM_PORT), '--relaxed-security'], + { + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + } + ); + + let started = false; + const timeout = setTimeout(() => { + if (!started) { + reject(new Error('Appium server failed to start within 30 seconds')); + } + }, 30000); + + this.appiumProcess.stdout?.on('data', (data: Buffer) => { + const output = data.toString(); + if (output.includes('Appium REST http interface listener started')) { + started = true; + clearTimeout(timeout); + resolve(); + } + }); + + this.appiumProcess.stderr?.on('data', (data: Buffer) => { + const output = data.toString(); + // Appium logs to stderr for info messages too + if (output.includes('Appium REST http interface listener started')) { + started = true; + clearTimeout(timeout); + resolve(); + } + }); + + this.appiumProcess.on('error', (err) => { + clearTimeout(timeout); + reject(new Error(`Failed to start Appium: ${err.message}`)); + }); + + this.appiumProcess.on('close', (code) => { + if (!started) { + clearTimeout(timeout); + reject(new Error(`Appium exited with code ${code}`)); + } + }); + }); + } + + /** + * Boot the iOS simulator + */ + private async bootSimulator(udid: string): Promise { + try { + const devices = await this.simctl.getDevices(); + let currentState: string | undefined; + + // Find current device state + for (const deviceList of Object.values(devices)) { + if (!Array.isArray(deviceList)) continue; + const device = (deviceList as any[]).find((d: any) => d.udid === udid); + if (device) { + currentState = device.state; + break; + } + } + + if (currentState === 'Booted') { + return; // Already booted + } + + // node-simctl expects udid to be set on the instance + this.simctl.udid = udid; + await this.simctl.bootDevice(); + + // Wait for device to be fully booted + let attempts = 0; + while (attempts < 60) { + const updatedDevices = await this.simctl.getDevices(); + for (const deviceList of Object.values(updatedDevices)) { + if (!Array.isArray(deviceList)) continue; + const device = (deviceList as any[]).find((d: any) => d.udid === udid); + if (device?.state === 'Booted') { + return; + } + } + await new Promise((r) => setTimeout(r, 1000)); + attempts++; + } + + throw new Error('Simulator failed to boot within 60 seconds'); + } catch (error) { + throw new Error( + `Failed to boot simulator: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + + /** + * Launch iOS Safari via Appium + */ + async launch( + options: { + device?: string; + udid?: string; + headless?: boolean; + } = {} + ): Promise { + if (this.isLaunched()) { + return; // Already launched + } + + // Find device + let device: IOSDeviceInfo | null = null; + + if (options.udid) { + device = await this.findDevice(options.udid); + if (!device) { + throw new Error(`Device with UDID ${options.udid} not found`); + } + } else if (options.device) { + device = await this.findDevice(options.device); + if (!device) { + throw new Error(`Device "${options.device}" not found. Run: agent-browser device list`); + } + } else { + // Check environment variable + const envDevice = process.env.AGENT_BROWSER_IOS_DEVICE; + const envUdid = process.env.AGENT_BROWSER_IOS_UDID; + + if (envUdid) { + device = await this.findDevice(envUdid); + if (!device) { + throw new Error(`Device with UDID ${envUdid} not found. Run: agent-browser device list`); + } + } else if (envDevice) { + device = await this.findDevice(envDevice); + if (!device) { + throw new Error(`Device "${envDevice}" not found. Run: agent-browser device list`); + } + } else { + device = await this.findDefaultDevice(); + if (!device) { + throw new Error( + 'No iOS simulators available. Open Xcode and download simulator runtimes.' + ); + } + } + } + + this.deviceUdid = device.udid; + this.deviceName = device.name; + + // Start Appium server + await this.startAppiumServer(); + + // Boot simulator (skip for real devices - they're already running) + if (!device.isRealDevice) { + await this.bootSimulator(device.udid); + } + + // Connect to Safari via Appium + try { + this.browser = await remote({ + hostname: IOSManager.APPIUM_HOST, + port: IOSManager.APPIUM_PORT, + path: '/', + capabilities: { + platformName: 'iOS', + 'appium:automationName': 'XCUITest', + 'appium:deviceName': device.name, + 'appium:udid': device.udid, + browserName: 'Safari', + 'appium:noReset': true, + 'appium:newCommandTimeout': 300, + }, + connectionRetryTimeout: 120000, + connectionRetryCount: 3, + }); + } catch (error) { + throw new Error( + `Failed to connect to Safari: ${error instanceof Error ? error.message : String(error)}. ` + + 'Make sure XCUITest driver is installed: appium driver install xcuitest' + ); + } + } + + /** + * Navigate to URL + */ + async navigate(url: string): Promise<{ url: string; title: string }> { + if (!this.browser) { + throw new Error('iOS browser not launched. Call launch first.'); + } + + await this.browser.url(url); + + // Wait for page to load + await this.browser.waitUntil( + async () => { + const state = (await this.browser!.execute( + 'return document.readyState' + )) as unknown as string; + return state === 'complete'; + }, + { timeout: 30000, interval: 500 } + ); + + const title = await this.browser.getTitle(); + const currentUrl = await this.browser.getUrl(); + + return { url: currentUrl, title }; + } + + /** + * Get current URL + */ + async getUrl(): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + return this.browser.getUrl(); + } + + /** + * Get page title + */ + async getTitle(): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + return this.browser.getTitle(); + } + + /** + * Click/tap an element + */ + async click(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + await element.click(); + } + + /** + * Alias for click (semantic clarity for touch) + */ + async tap(selector: string): Promise { + return this.click(selector); + } + + /** + * Type text into an element + */ + async type( + selector: string, + text: string, + options?: { delay?: number; clear?: boolean } + ): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + + if (options?.clear) { + await element.clearValue(); + } + + // WebdriverIO doesn't have a delay option, so we simulate it + if (options?.delay && options.delay > 0) { + for (const char of text) { + await element.addValue(char); + await new Promise((r) => setTimeout(r, options.delay)); + } + } else { + await element.addValue(text); + } + } + + /** + * Fill an element (clear first, then type) + */ + async fill(selector: string, value: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + await element.clearValue(); + await element.setValue(value); + } + + /** + * Get element by selector or ref + */ + private async getElement(selectorOrRef: string) { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + // Check if it's a ref + const refData = this.getRefData(selectorOrRef); + if (refData) { + if (refData.xpath) { + return this.browser.$(refData.xpath); + } + return this.browser.$(refData.selector); + } + + // Regular CSS selector + return this.browser.$(selectorOrRef); + } + + /** + * Get ref data from ref string + */ + private getRefData(refArg: string): IOSRefMap[string] | null { + let ref: string | null = null; + + if (refArg.startsWith('@')) { + ref = refArg.slice(1); + } else if (refArg.startsWith('ref=')) { + ref = refArg.slice(4); + } else if (/^e\d+$/.test(refArg)) { + ref = refArg; + } + + if (ref && this.refMap[ref]) { + return this.refMap[ref]; + } + + return null; + } + + /** + * Take a screenshot + */ + async screenshot(options?: { + path?: string; + fullPage?: boolean; + }): Promise<{ path?: string; base64?: string }> { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const base64 = await this.browser.takeScreenshot(); + + if (options?.path) { + const { writeFileSync, mkdirSync } = await import('node:fs'); + const dir = path.dirname(options.path); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + writeFileSync(options.path, base64, 'base64'); + return { path: options.path }; + } + + return { base64 }; + } + + /** + * Get page snapshot with refs + */ + async getSnapshot(options?: { interactive?: boolean }): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + this.refCounter = 0; + this.refMap = {}; + + // Get page structure via JavaScript execution + // Note: The function runs in browser context, so we use 'any' for DOM types + const snapshot = await this.browser.execute(function (interactiveOnly: boolean): any { + const INTERACTIVE_ROLES = new Set([ + 'button', + 'link', + 'textbox', + 'checkbox', + 'radio', + 'combobox', + 'listbox', + 'menuitem', + 'option', + 'searchbox', + 'slider', + 'spinbutton', + 'switch', + 'tab', + 'treeitem', + ]); + + const INTERACTIVE_TAGS = new Set([ + 'A', + 'BUTTON', + 'INPUT', + 'SELECT', + 'TEXTAREA', + 'DETAILS', + 'SUMMARY', + ]); + + function getXPath(element: any): string { + if (element.id) { + return `//*[@id="${element.id}"]`; + } + + const parts: string[] = []; + let current: any = element; + + while (current && current.nodeType === 1) { + // Node.ELEMENT_NODE = 1 + let index = 1; + let sibling: any = current.previousElementSibling; + + while (sibling) { + if (sibling.nodeName === current.nodeName) { + index++; + } + sibling = sibling.previousElementSibling; + } + + const tagName = current.nodeName.toLowerCase(); + parts.unshift(`${tagName}[${index}]`); + current = current.parentElement; + } + + return '/' + parts.join('/'); + } + + function getAccessibleName(element: any): string { + // aria-label takes precedence + const ariaLabel = element.getAttribute('aria-label'); + if (ariaLabel) return ariaLabel; + + // For inputs, check placeholder and label + const tagName = element.tagName; + if (tagName === 'INPUT' || tagName === 'TEXTAREA') { + const id = element.id; + if (id) { + const label = (document as any).querySelector(`label[for="${id}"]`); + if (label) return label.textContent?.trim() || ''; + } + if (element.placeholder) return element.placeholder; + } + + // For buttons and links, use text content + if (tagName === 'BUTTON' || tagName === 'A') { + return element.textContent?.trim() || ''; + } + + // aria-labelledby + const labelledBy = element.getAttribute('aria-labelledby'); + if (labelledBy) { + const labelElement = (document as any).getElementById(labelledBy); + if (labelElement) return labelElement.textContent?.trim() || ''; + } + + return element.textContent?.trim().slice(0, 50) || ''; + } + + function getRole(element: any): string | null { + // Explicit role + const role = element.getAttribute('role'); + if (role) return role; + + // Implicit roles + const tag = element.tagName; + if (tag === 'A' && element.hasAttribute('href')) return 'link'; + if (tag === 'BUTTON') return 'button'; + if (tag === 'INPUT') { + const type = element.type; + if (type === 'checkbox') return 'checkbox'; + if (type === 'radio') return 'radio'; + if (type === 'text' || type === 'email' || type === 'password' || type === 'search') + return 'textbox'; + if (type === 'submit' || type === 'button') return 'button'; + } + if (tag === 'TEXTAREA') return 'textbox'; + if (tag === 'SELECT') return 'combobox'; + if ( + tag === 'H1' || + tag === 'H2' || + tag === 'H3' || + tag === 'H4' || + tag === 'H5' || + tag === 'H6' + ) + return 'heading'; + if (tag === 'IMG') return 'img'; + if (tag === 'NAV') return 'navigation'; + if (tag === 'MAIN') return 'main'; + if (tag === 'HEADER') return 'banner'; + if (tag === 'FOOTER') return 'contentinfo'; + + return null; + } + + function traverse(element: any, depth: number): any { + if (depth > 10) return null; // Limit depth + + const tag = element.tagName; + const role = getRole(element); + const name = getAccessibleName(element); + const isInteractive = + INTERACTIVE_TAGS.has(tag) || (role !== null && INTERACTIVE_ROLES.has(role)); + + // Skip hidden elements + const style = (window as any).getComputedStyle(element); + if (style.display === 'none' || style.visibility === 'hidden') { + return null; + } + + const children: any[] = []; + for (const child of element.children) { + const childInfo = traverse(child, depth + 1); + if (childInfo) { + children.push(childInfo); + } + } + + // In interactive mode, skip non-interactive elements without interactive children + if (interactiveOnly && !isInteractive && children.length === 0) { + return null; + } + + return { + tag, + role, + name, + text: element.textContent?.trim().slice(0, 100) || '', + isInteractive, + xpath: getXPath(element), + children, + }; + } + + const root = traverse((document as any).body, 0); + return root; + }, options?.interactive ?? false); + + // Build tree string and refs + const lines: string[] = []; + const buildTree = (node: any, indent: number) => { + if (!node) return; + + const prefix = ' '.repeat(indent) + '- '; + const role = node.role || node.tag.toLowerCase(); + const name = node.name; + + let line = `${prefix}${role}`; + if (name) { + line += ` "${name}"`; + } + + // Add ref for interactive elements + if (node.isInteractive) { + const ref = `e${++this.refCounter}`; + line += ` [ref=${ref}]`; + + this.refMap[ref] = { + selector: node.xpath.startsWith('/') ? node.xpath : `#${node.xpath}`, + role: node.role, + name: node.name, + xpath: node.xpath, + }; + } + + lines.push(line); + + for (const child of node.children || []) { + buildTree(child, indent + 1); + } + }; + + if (snapshot) { + buildTree(snapshot, 0); + } + + const tree = lines.join('\n') || '(empty)'; + this.lastSnapshot = tree; + + return { tree, refs: this.refMap }; + } + + /** + * Get cached ref map + */ + getRefMap(): IOSRefMap { + return this.refMap; + } + + /** + * Scroll the page + */ + async scroll(options?: { + selector?: string; + x?: number; + y?: number; + direction?: 'up' | 'down' | 'left' | 'right'; + amount?: number; + }): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const amount = options?.amount ?? 300; + + if (options?.selector) { + const element = await this.getElement(options.selector); + await element.scrollIntoView(); + return; + } + + // Use JavaScript scrolling + let deltaX = options?.x ?? 0; + let deltaY = options?.y ?? 0; + + if (options?.direction) { + switch (options.direction) { + case 'up': + deltaY = -amount; + break; + case 'down': + deltaY = amount; + break; + case 'left': + deltaX = -amount; + break; + case 'right': + deltaX = amount; + break; + } + } + + await this.browser.execute( + function (x: number, y: number) { + (window as any).scrollBy(x, y); + }, + deltaX, + deltaY + ); + } + + /** + * Swipe gesture (iOS-specific) + */ + async swipe( + direction: 'up' | 'down' | 'left' | 'right', + options?: { distance?: number } + ): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const distance = options?.distance ?? 300; + + // Map direction to scroll (opposite direction) + const scrollDirection = { + up: 'down', + down: 'up', + left: 'right', + right: 'left', + }[direction] as 'up' | 'down' | 'left' | 'right'; + + await this.scroll({ direction: scrollDirection, amount: distance }); + } + + /** + * Execute JavaScript + */ + async evaluate(script: string, ...args: unknown[]): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + // Execute the script directly - WebdriverIO handles the context + const result = await this.browser.execute( + function (code: string, evalArgs: any[]) { + // Create a function from the code and execute it + const fn = new Function(...evalArgs.map((_: any, i: number) => `arg${i}`), code); + return fn(...evalArgs); + }, + script.includes('return') ? script : `return (${script})`, + args + ); + + return result as T; + } + + /** + * Wait for element + */ + async wait(options: { + selector?: string; + timeout?: number; + state?: 'attached' | 'detached' | 'visible' | 'hidden'; + }): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const timeout = options.timeout ?? 30000; + + if (options.selector) { + const element = await this.getElement(options.selector); + + switch (options.state) { + case 'detached': + await element.waitForExist({ timeout, reverse: true }); + break; + case 'hidden': + await element.waitForDisplayed({ timeout, reverse: true }); + break; + case 'visible': + await element.waitForDisplayed({ timeout }); + break; + case 'attached': + default: + await element.waitForExist({ timeout }); + break; + } + } else { + // Just wait for timeout + await new Promise((r) => setTimeout(r, timeout)); + } + } + + /** + * Press a key + */ + async press(key: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + // Map common key names + const keyMap: Record = { + Enter: '\uE007', + Tab: '\uE004', + Escape: '\uE00C', + Backspace: '\uE003', + Delete: '\uE017', + ArrowUp: '\uE013', + ArrowDown: '\uE015', + ArrowLeft: '\uE012', + ArrowRight: '\uE014', + }; + + const mappedKey = keyMap[key] ?? key; + await this.browser.keys(mappedKey); + } + + /** + * Hover over element (limited on touch - just scrolls into view) + */ + async hover(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + await element.scrollIntoView(); + } + + /** + * Get page content (HTML) + */ + async getContent(selector?: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + if (selector) { + const element = await this.getElement(selector); + return element.getHTML(); + } + + return this.browser.getPageSource(); + } + + /** + * Get text content of element + */ + async getText(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + return element.getText(); + } + + /** + * Get attribute value + */ + async getAttribute(selector: string, attribute: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + return element.getAttribute(attribute); + } + + /** + * Check if element is visible + */ + async isVisible(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + try { + const element = await this.getElement(selector); + return element.isDisplayed(); + } catch { + return false; + } + } + + /** + * Check if element is enabled + */ + async isEnabled(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + return element.isEnabled(); + } + + /** + * Navigate back + */ + async goBack(): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + await this.browser.back(); + } + + /** + * Navigate forward + */ + async goForward(): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + await this.browser.forward(); + } + + /** + * Reload page + */ + async reload(): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + await this.browser.refresh(); + } + + /** + * Select option(s) from dropdown + */ + async select(selector: string, values: string | string[]): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + const valueArray = Array.isArray(values) ? values : [values]; + + for (const value of valueArray) { + await element.selectByAttribute('value', value); + } + } + + /** + * Check a checkbox + */ + async check(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + const isChecked = await element.isSelected(); + if (!isChecked) { + await element.click(); + } + } + + /** + * Uncheck a checkbox + */ + async uncheck(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + const isChecked = await element.isSelected(); + if (isChecked) { + await element.click(); + } + } + + /** + * Focus an element + */ + async focus(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + await this.browser.execute(function (el: any) { + el.focus(); + }, element as any); + } + + /** + * Clear input field + */ + async clear(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const element = await this.getElement(selector); + await element.clearValue(); + } + + /** + * Get element count + */ + async count(selector: string): Promise { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + const elements = await this.browser.$$(selector); + return elements.length; + } + + /** + * Get bounding box + */ + async getBoundingBox( + selector: string + ): Promise<{ x: number; y: number; width: number; height: number } | null> { + if (!this.browser) { + throw new Error('iOS browser not launched'); + } + + try { + const element = await this.getElement(selector); + const location = await element.getLocation(); + const size = await element.getSize(); + return { + x: location.x, + y: location.y, + width: size.width, + height: size.height, + }; + } catch { + return null; + } + } + + /** + * Get device info + */ + getDeviceInfo(): { name: string; udid: string } | null { + if (!this.deviceName || !this.deviceUdid) { + return null; + } + return { + name: this.deviceName, + udid: this.deviceUdid, + }; + } + + /** + * Close browser and cleanup + */ + async close(): Promise { + if (this.browser) { + try { + await this.browser.deleteSession(); + } catch { + // Ignore cleanup errors + } + this.browser = null; + } + + if (this.appiumProcess) { + this.appiumProcess.kill(); + this.appiumProcess = null; + } + + // Optionally shutdown simulator + if (this.deviceUdid) { + try { + this.simctl.udid = this.deviceUdid; + await this.simctl.shutdownDevice(); + } catch { + // Ignore - simulator might already be shutdown + } + } + + this.deviceUdid = null; + this.deviceName = null; + this.refMap = {}; + this.lastSnapshot = ''; + this.refCounter = 0; + } +} diff --git a/src/protocol.ts b/src/protocol.ts index 9c1091a8..52fe07d3 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -686,6 +686,17 @@ const inputTouchSchema = baseCommandSchema.extend({ modifiers: z.number().optional(), }); +// iOS-specific schemas +const swipeSchema = baseCommandSchema.extend({ + action: z.literal('swipe'), + direction: z.enum(['up', 'down', 'left', 'right']), + distance: z.number().positive().optional(), +}); + +const deviceListSchema = baseCommandSchema.extend({ + action: z.literal('device_list'), +}); + const pressSchema = baseCommandSchema.extend({ action: z.literal('press'), key: z.string().min(1), @@ -906,6 +917,8 @@ const commandSchema = z.discriminatedUnion('action', [ inputMouseSchema, inputKeyboardSchema, inputTouchSchema, + swipeSchema, + deviceListSchema, ]); // Parse result type diff --git a/src/types.ts b/src/types.ts index 11331635..d8c2834f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -521,6 +521,17 @@ export interface InputTouchCommand extends BaseCommand { modifiers?: number; } +// iOS-specific commands +export interface SwipeCommand extends BaseCommand { + action: 'swipe'; + direction: 'up' | 'down' | 'left' | 'right'; + distance?: number; +} + +export interface DeviceListCommand extends BaseCommand { + action: 'device_list'; +} + // Video recording (Playwright native - requires launch-time setup) export interface VideoStartCommand extends BaseCommand { action: 'video_start'; @@ -931,7 +942,9 @@ export type Command = | ScreencastStopCommand | InputMouseCommand | InputKeyboardCommand - | InputTouchCommand; + | InputTouchCommand + | SwipeCommand + | DeviceListCommand; // Response types export interface SuccessResponse {