diff --git a/apps/desktop/src-tauri/DEEPLINKS.md b/apps/desktop/src-tauri/DEEPLINKS.md new file mode 100644 index 00000000000..2f2f937b1e2 --- /dev/null +++ b/apps/desktop/src-tauri/DEEPLINKS.md @@ -0,0 +1,71 @@ +# Cap Desktop Deeplinks + +Cap Desktop registers the `cap-desktop://` URL scheme. + +## Recording controls + +### Start recording + +```bash +open 'cap-desktop://record/start?mode=studio&capture_type=screen&target=Built-in%20Display&capture_system_audio=true&mic_label=External%20Microphone' +``` + +Query params: +- `mode`: `studio` or `instant` (default: `studio`) +- `capture_type`: `screen` or `window` (required) +- `target`: screen/window name exactly as shown in Cap (required) +- `capture_system_audio`: `true` / `false` (optional) +- `mic_label`: microphone label exactly as shown in Cap (optional) +- `mic_off=true` disables microphone input and cannot be combined with `mic_label` +- omitting `mic_label`, `device_id`, `model_id`, `off`, and `mic_off` keeps the current Cap inputs unchanged +- camera: + - `device_id=` or `model_id=` + - `off=true` to disable camera + +### Stop / pause / resume / toggle / restart + +```bash +open 'cap-desktop://record/stop' +open 'cap-desktop://record/pause' +open 'cap-desktop://record/resume' +open 'cap-desktop://record/toggle-pause' +open 'cap-desktop://record/restart' +``` + +## Device switching + +### Switch microphone + +```bash +open 'cap-desktop://device/microphone?label=External%20Microphone' +``` + +To disable microphone input: + +```bash +open 'cap-desktop://device/microphone?off=true' +``` + +### Switch camera + +```bash +open 'cap-desktop://device/camera?device_id=YOUR_DEVICE_ID' +open 'cap-desktop://device/camera?model_id=VID:PID' +open 'cap-desktop://device/camera?off=true' +``` + +`off=true` cannot be combined with `device_id` or `model_id`. + +## Settings + +```bash +open 'cap-desktop://settings/open?page=hotkeys' +``` + +## Backward compatibility + +Legacy JSON payload links remain supported: + +```bash +open 'cap-desktop://action?value={...json...}' +``` diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index a1170284877..c457e86c1e9 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -2,9 +2,12 @@ use cap_recording::{ RecordingMode, feeds::camera::DeviceOrModelID, sources::screen_capture::ScreenCaptureTarget, }; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; use tauri::{AppHandle, Manager, Url}; -use tracing::trace; +use tracing::{error, trace}; use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow}; @@ -25,7 +28,26 @@ pub enum DeepLinkAction { capture_system_audio: bool, mode: RecordingMode, }, + StartRecordingPath { + capture_mode: CaptureMode, + camera: Option, + apply_camera: bool, + mic_label: Option, + apply_mic: bool, + capture_system_audio: bool, + mode: RecordingMode, + }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + RestartRecording, + SwitchMicrophone { + mic_label: Option, + }, + SwitchCamera { + camera: Option, + }, OpenEditor { project_path: PathBuf, }, @@ -44,12 +66,11 @@ pub fn handle(app_handle: &AppHandle, urls: Vec) { DeepLinkAction::try_from(&url) .map_err(|e| match e { ActionParseFromUrlError::ParseFailed(msg) => { - eprintln!("Failed to parse deep link \"{}\": {}", &url, msg) + error!("Failed to parse deep link \"{}\": {}", &url, msg) } ActionParseFromUrlError::Invalid => { - eprintln!("Invalid deep link format \"{}\"", &url) + error!("Invalid deep link format \"{}\"", &url) } - // Likely login action, not handled here. ActionParseFromUrlError::NotAction => {} }) .ok() @@ -64,18 +85,136 @@ pub fn handle(app_handle: &AppHandle, urls: Vec) { tauri::async_runtime::spawn(async move { for action in actions { if let Err(e) = action.execute(&app_handle).await { - eprintln!("Failed to handle deep link action: {e}"); + error!("Failed to handle deep link action: {e}"); } } }); } +#[derive(Debug)] pub enum ActionParseFromUrlError { ParseFailed(String), Invalid, NotAction, } +fn parse_optional_string(value: Option<&String>) -> Option { + value + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .map(ToString::to_string) +} + +fn parse_bool(value: Option<&String>, default: bool) -> Result { + let Some(value) = value else { + return Ok(default); + }; + + match value.trim().to_ascii_lowercase().as_str() { + "1" | "true" | "yes" | "on" => Ok(true), + "0" | "false" | "no" | "off" => Ok(false), + _ => Err(ActionParseFromUrlError::ParseFailed(format!( + "invalid boolean value '{value}'" + ))), + } +} + +fn parse_mode(value: Option<&String>) -> Result { + match value + .map(|value| value.trim().to_ascii_lowercase()) + .as_deref() + .unwrap_or("studio") + { + "studio" => Ok(RecordingMode::Studio), + "instant" => Ok(RecordingMode::Instant), + other => Err(ActionParseFromUrlError::ParseFailed(format!( + "invalid mode '{other}', expected 'studio' or 'instant'" + ))), + } +} + +fn query_map(url: &Url) -> HashMap { + url.query_pairs() + .map(|(key, value)| (key.to_string(), value.to_string())) + .collect() +} + +fn path_action(url: &Url) -> Option<(String, String)> { + if url.scheme() != "cap-desktop" { + return None; + } + + let domain = url.domain()?.to_string(); + let mut segments = url.path_segments()?; + let action = segments.next()?.trim().to_string(); + + if action.is_empty() { + return None; + } + + Some((domain, action)) +} + +fn parse_camera_from_query( + query: &HashMap, +) -> Result, ActionParseFromUrlError> { + let device_id = parse_optional_string(query.get("device_id")); + let model_id = parse_optional_string(query.get("model_id")); + let off = parse_bool(query.get("off"), false)?; + + if off && (device_id.is_some() || model_id.is_some()) { + return Err(ActionParseFromUrlError::ParseFailed( + "camera deep link cannot combine 'off=true' with 'device_id' or 'model_id'".to_string(), + )); + } + + if off { + return Ok(None); + } + + match (device_id, model_id) { + (Some(device_id), None) => Ok(Some(DeviceOrModelID::DeviceID(device_id))), + (None, Some(model_id)) => { + let parsed_model_id = model_id.try_into().map_err(|_| { + ActionParseFromUrlError::ParseFailed( + "invalid 'model_id' format, expected 'VID:PID'".to_string(), + ) + })?; + Ok(Some(DeviceOrModelID::ModelID(parsed_model_id))) + } + (None, None) => Ok(None), + (Some(_), Some(_)) => Err(ActionParseFromUrlError::ParseFailed( + "camera deep link can specify only one of 'device_id' or 'model_id'".to_string(), + )), + } +} + +fn parse_microphone_from_query( + query: &HashMap, + label_key: &str, + off_key: &str, +) -> Result<(Option, bool), ActionParseFromUrlError> { + let label_present = query.contains_key(label_key); + let label = parse_optional_string(query.get(label_key)); + let off = parse_bool(query.get(off_key), false)?; + + if off && label_present { + return Err(ActionParseFromUrlError::ParseFailed(format!( + "microphone deep link cannot combine '{off_key}=true' with '{label_key}'" + ))); + } + + if off { + return Ok((None, true)); + } + + if label_present { + return Ok((label, true)); + } + + Ok((None, false)) +} + impl TryFrom<&Url> for DeepLinkAction { type Error = ActionParseFromUrlError; @@ -88,14 +227,98 @@ impl TryFrom<&Url> for DeepLinkAction { .map_err(|_| ActionParseFromUrlError::Invalid); } + if let Some((domain, action)) = path_action(url) { + let query = query_map(url); + + return match (domain.as_str(), action.as_str()) { + ("record", "start") => { + let capture_type = parse_optional_string(query.get("capture_type")) + .ok_or_else(|| { + ActionParseFromUrlError::ParseFailed( + "missing required query parameter 'capture_type'".to_string(), + ) + })? + .to_ascii_lowercase(); + let target = parse_optional_string(query.get("target")).ok_or_else(|| { + ActionParseFromUrlError::ParseFailed( + "missing required query parameter 'target'".to_string(), + ) + })?; + let capture_mode = match capture_type.as_str() { + "screen" => CaptureMode::Screen(target), + "window" => CaptureMode::Window(target), + _ => { + return Err(ActionParseFromUrlError::ParseFailed( + "capture_type must be 'screen' or 'window'".to_string(), + )); + } + }; + let apply_camera = query.contains_key("device_id") + || query.contains_key("model_id") + || query.contains_key("off"); + let (mic_label, apply_mic) = + parse_microphone_from_query(&query, "mic_label", "mic_off")?; + + Ok(Self::StartRecordingPath { + capture_mode, + camera: parse_camera_from_query(&query)?, + apply_camera, + mic_label, + apply_mic, + capture_system_audio: parse_bool( + query.get("capture_system_audio"), + false, + )?, + mode: parse_mode(query.get("mode"))?, + }) + } + ("record", "stop") => Ok(Self::StopRecording), + ("record", "pause") => Ok(Self::PauseRecording), + ("record", "resume") => Ok(Self::ResumeRecording), + ("record", "toggle-pause") => Ok(Self::TogglePauseRecording), + ("record", "restart") => Ok(Self::RestartRecording), + ("device", "microphone") => { + let (mic_label, apply_mic) = + parse_microphone_from_query(&query, "label", "off")?; + if !apply_mic { + return Err(ActionParseFromUrlError::ParseFailed( + "device/microphone requires 'label' or 'off=true'".to_string(), + )); + } + Ok(Self::SwitchMicrophone { mic_label }) + } + ("device", "camera") => { + let has_camera_param = query.contains_key("device_id") + || query.contains_key("model_id") + || query.contains_key("off"); + if !has_camera_param { + return Err(ActionParseFromUrlError::ParseFailed( + "device/camera requires 'device_id', 'model_id', or 'off=true'" + .to_string(), + )); + } + Ok(Self::SwitchCamera { + camera: parse_camera_from_query(&query)?, + }) + } + ("settings", "open") => Ok(Self::OpenSettings { + page: parse_optional_string(query.get("page")), + }), + _ => Err(ActionParseFromUrlError::NotAction), + }; + } + + if url.scheme() != "cap-desktop" { + return Err(ActionParseFromUrlError::NotAction); + } + match url.domain() { - Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction), - _ => Err(ActionParseFromUrlError::Invalid), + Some("action") => Ok(()), + Some(_) => Err(ActionParseFromUrlError::NotAction), + None => Err(ActionParseFromUrlError::Invalid), }?; - let params = url - .query_pairs() - .collect::>(); + let params = url.query_pairs().collect::>(); let json_value = params .get("value") .ok_or(ActionParseFromUrlError::Invalid)?; @@ -123,13 +346,55 @@ impl DeepLinkAction { let capture_target: ScreenCaptureTarget = match capture_mode { CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() .into_iter() - .find(|(s, _)| s.name == name) - .map(|(s, _)| ScreenCaptureTarget::Display { id: s.id }) + .find(|(screen, _)| screen.name == name) + .map(|(screen, _)| ScreenCaptureTarget::Display { id: screen.id }) .ok_or(format!("No screen with name \"{}\"", &name))?, CaptureMode::Window(name) => cap_recording::screen_capture::list_windows() .into_iter() - .find(|(w, _)| w.name == name) - .map(|(w, _)| ScreenCaptureTarget::Window { id: w.id }) + .find(|(window, _)| window.name == name) + .map(|(window, _)| ScreenCaptureTarget::Window { id: window.id }) + .ok_or(format!("No window with name \"{}\"", &name))?, + }; + + let inputs = StartRecordingInputs { + mode, + capture_target, + capture_system_audio, + organization_id: None, + }; + + crate::recording::start_recording(app.clone(), state, inputs) + .await + .map(|_| ()) + } + DeepLinkAction::StartRecordingPath { + capture_mode, + camera, + apply_camera, + mic_label, + apply_mic, + capture_system_audio, + mode, + } => { + let state = app.state::>(); + + if apply_camera { + crate::set_camera_input(app.clone(), state.clone(), camera, None).await?; + } + if apply_mic { + crate::set_mic_input(state.clone(), mic_label).await?; + } + + let capture_target: ScreenCaptureTarget = match capture_mode { + CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() + .into_iter() + .find(|(screen, _)| screen.name == name) + .map(|(screen, _)| ScreenCaptureTarget::Display { id: screen.id }) + .ok_or(format!("No screen with name \"{}\"", &name))?, + CaptureMode::Window(name) => cap_recording::screen_capture::list_windows() + .into_iter() + .find(|(window, _)| window.name == name) + .map(|(window, _)| ScreenCaptureTarget::Window { id: window.id }) .ok_or(format!("No window with name \"{}\"", &name))?, }; @@ -147,6 +412,26 @@ impl DeepLinkAction { DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await } + DeepLinkAction::PauseRecording => { + crate::recording::pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::ResumeRecording => { + crate::recording::resume_recording(app.clone(), app.state()).await + } + DeepLinkAction::TogglePauseRecording => { + crate::recording::toggle_pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::RestartRecording => { + crate::recording::restart_recording(app.clone(), app.state()) + .await + .map(|_| ()) + } + DeepLinkAction::SwitchMicrophone { mic_label } => { + crate::set_mic_input(app.state(), mic_label).await + } + DeepLinkAction::SwitchCamera { camera } => { + crate::set_camera_input(app.clone(), app.state::>(), camera, None).await + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } @@ -156,3 +441,207 @@ impl DeepLinkAction { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_start_recording_path_deeplink() { + let url = Url::parse( + "cap-desktop://record/start?mode=studio&capture_type=screen&target=Built-in+Display&capture_system_audio=true&mic_label=MacBook+Mic", + ) + .unwrap(); + + let action = DeepLinkAction::try_from(&url).unwrap(); + + match action { + DeepLinkAction::StartRecordingPath { + capture_mode, + mic_label, + apply_camera, + apply_mic, + capture_system_audio, + mode, + .. + } => { + assert!(matches!(mode, RecordingMode::Studio)); + assert!(capture_system_audio); + assert_eq!(mic_label, Some("MacBook Mic".to_string())); + assert!(apply_mic); + assert!(!apply_camera); + assert!(matches!(capture_mode, CaptureMode::Screen(name) if name == "Built-in Display")); + } + _ => panic!("expected StartRecordingPath action"), + } + } + + #[test] + fn parses_switch_camera_device_id() { + let url = Url::parse("cap-desktop://device/camera?device_id=abc").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::SwitchCamera { + camera: Some(DeviceOrModelID::DeviceID(id)) + } if id == "abc" + )); + } + + #[test] + fn rejects_camera_with_both_device_and_model_id() { + let url = Url::parse("cap-desktop://device/camera?device_id=abc&model_id=def").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::ParseFailed(_)))); + } + + #[test] + fn rejects_camera_off_with_device_id() { + let url = Url::parse("cap-desktop://device/camera?off=true&device_id=abc").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::ParseFailed(_)))); + } + + #[test] + fn parses_switch_microphone_off() { + let url = Url::parse("cap-desktop://device/microphone?off=true").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::SwitchMicrophone { mic_label: None } + )); + } + + #[test] + fn keeps_legacy_action_json_deeplink() { + let url = Url::parse("cap-desktop://action?value=%22stop_recording%22").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!(action, DeepLinkAction::StopRecording)); + } + + #[test] + fn rejects_wrong_scheme() { + let url = Url::parse("cap://record/stop").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::NotAction))); + } + + #[test] + fn rejects_microphone_with_no_params() { + // Regression test: a bare `cap-desktop://device/microphone` URL must not + // silently disable an active microphone. It must fail parsing so the + // caller receives a clear error instead of an accidental mic-off. + let url = Url::parse("cap-desktop://device/microphone").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::ParseFailed(_)))); + } + + #[test] + fn rejects_camera_with_no_params() { + // Regression test mirroring rejects_microphone_with_no_params: a bare + // `cap-desktop://device/camera` URL must not silently disable an + // active camera. `SwitchCamera { camera: None }` is only produced + // when the caller explicitly asks for `off=true`. + let url = Url::parse("cap-desktop://device/camera").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::ParseFailed(_)))); + } + + #[test] + fn parses_switch_camera_off() { + let url = Url::parse("cap-desktop://device/camera?off=true").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::SwitchCamera { camera: None } + )); + } + + #[test] + fn parses_switch_microphone_label() { + let url = + Url::parse("cap-desktop://device/microphone?label=MacBook+Mic").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::SwitchMicrophone { mic_label: Some(ref l) } if l == "MacBook Mic" + )); + } + + #[test] + fn parses_stop_pause_resume_restart_toggle() { + let cases = [ + ("cap-desktop://record/stop", DeepLinkAction::StopRecording), + ("cap-desktop://record/pause", DeepLinkAction::PauseRecording), + ("cap-desktop://record/resume", DeepLinkAction::ResumeRecording), + ( + "cap-desktop://record/toggle-pause", + DeepLinkAction::TogglePauseRecording, + ), + ( + "cap-desktop://record/restart", + DeepLinkAction::RestartRecording, + ), + ]; + + for (raw, expected) in cases { + let url = Url::parse(raw).unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + assert!( + std::mem::discriminant(&action) == std::mem::discriminant(&expected), + "unexpected action for {raw}", + ); + } + } + + #[test] + fn parses_settings_open_with_page() { + let url = Url::parse("cap-desktop://settings/open?page=general").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::OpenSettings { page: Some(ref p) } if p == "general" + )); + } + + #[test] + fn parses_settings_open_without_page() { + let url = Url::parse("cap-desktop://settings/open").unwrap(); + let action = DeepLinkAction::try_from(&url).unwrap(); + + assert!(matches!( + action, + DeepLinkAction::OpenSettings { page: None } + )); + } + + #[test] + fn rejects_unknown_path_domain() { + let url = Url::parse("cap-desktop://bogus/route").unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::NotAction))); + } + + #[test] + fn rejects_start_recording_invalid_capture_type() { + let url = Url::parse( + "cap-desktop://record/start?mode=studio&capture_type=speaker&target=Display", + ) + .unwrap(); + let action = DeepLinkAction::try_from(&url); + + assert!(matches!(action, Err(ActionParseFromUrlError::ParseFailed(_)))); + } +} diff --git a/apps/raycast/README.md b/apps/raycast/README.md new file mode 100644 index 00000000000..0488f14cb29 --- /dev/null +++ b/apps/raycast/README.md @@ -0,0 +1,19 @@ +# Cap Raycast Extension + +This extension controls Cap with `cap-desktop://` deeplinks. + +Commands: +- Start recording +- Stop recording +- Pause recording +- Resume recording +- Toggle pause recording +- Restart recording +- Switch microphone +- Switch camera +- Open settings + +Notes: +- Target names and device identifiers must match what Cap expects. +- The extension never uses `cap://`; it always sends `cap-desktop://` deeplinks. +- Camera switching supports `device_id`, `model_id`, or `off=true`. diff --git a/apps/raycast/demo/cap-raycast-demo.mp4 b/apps/raycast/demo/cap-raycast-demo.mp4 new file mode 100644 index 00000000000..7b07e919bb6 Binary files /dev/null and b/apps/raycast/demo/cap-raycast-demo.mp4 differ diff --git a/apps/raycast/icon.png b/apps/raycast/icon.png new file mode 100644 index 00000000000..b1ac1ef7d8a Binary files /dev/null and b/apps/raycast/icon.png differ diff --git a/apps/raycast/package.json b/apps/raycast/package.json new file mode 100644 index 00000000000..0090f9c721c --- /dev/null +++ b/apps/raycast/package.json @@ -0,0 +1,89 @@ +{ + "name": "@cap/raycast", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "build": "ray build", + "lint": "ray lint", + "dev": "ray develop", + "typecheck": "tsc --noEmit" + }, + "raycast": { + "schemaVersion": 1, + "title": "Cap Recorder", + "description": "Control Cap through cap-desktop deeplinks", + "icon": "icon.png", + "author": "Cap", + "categories": [ + "Productivity" + ], + "commands": [ + { + "name": "start-recording", + "title": "Start Cap Recording", + "description": "Start a Cap recording via deeplink", + "mode": "view" + }, + { + "name": "stop-recording", + "title": "Stop Cap Recording", + "description": "Stop the current Cap recording", + "mode": "no-view" + }, + { + "name": "pause-recording", + "title": "Pause Cap Recording", + "description": "Pause the current Cap recording", + "mode": "no-view" + }, + { + "name": "resume-recording", + "title": "Resume Cap Recording", + "description": "Resume the current Cap recording", + "mode": "no-view" + }, + { + "name": "toggle-pause-recording", + "title": "Toggle Pause Cap Recording", + "description": "Toggle pause for the current Cap recording", + "mode": "no-view" + }, + { + "name": "restart-recording", + "title": "Restart Cap Recording", + "description": "Restart the current Cap recording", + "mode": "no-view" + }, + { + "name": "switch-microphone", + "title": "Switch Cap Microphone", + "description": "Switch the active microphone in Cap", + "mode": "view" + }, + { + "name": "switch-camera", + "title": "Switch Cap Camera", + "description": "Switch the active camera in Cap", + "mode": "view" + }, + { + "name": "open-settings", + "title": "Open Cap Settings", + "description": "Open Cap settings via deeplink", + "mode": "view" + } + ] + }, + "dependencies": { + "@raycast/api": "^1.95.0", + "react": "19.0.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.0.2", + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "eslint": "^8.57.0", + "typescript": "^5.8.3" + } +} diff --git a/apps/raycast/src/deeplink.ts b/apps/raycast/src/deeplink.ts new file mode 100644 index 00000000000..b16e052d4f2 --- /dev/null +++ b/apps/raycast/src/deeplink.ts @@ -0,0 +1,53 @@ +import { execFile } from "node:child_process"; +import { closeMainWindow, showHUD, showToast, Toast } from "@raycast/api"; + +function openUrl(url: string): Promise { + return new Promise((resolve, reject) => { + execFile("open", [url], (error) => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); +} + +export function buildCapUrl( + path: string, + params?: Record, +) { + const normalizedPath = path.replace(/^\/+/, ""); + const url = new URL(`cap-desktop://${normalizedPath}`); + + if (params) { + for (const [key, value] of Object.entries(params)) { + const normalizedValue = value?.trim(); + if (normalizedValue) { + url.searchParams.set(key, normalizedValue); + } + } + } + + return url.toString(); +} + +export async function sendCapDeepLink( + path: string, + params?: Record, +) { + const url = buildCapUrl(path, params); + + try { + await openUrl(url); + await closeMainWindow(); + await showHUD("Sent to Cap"); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to open Cap deeplink", + message: error instanceof Error ? error.message : String(error), + }); + throw error; + } +} diff --git a/apps/raycast/src/form.ts b/apps/raycast/src/form.ts new file mode 100644 index 00000000000..91cbc67cb1f --- /dev/null +++ b/apps/raycast/src/form.ts @@ -0,0 +1,13 @@ +export type FormValues = Record; + +export function getString(values: FormValues, id: string) { + const value = values[id]; + return typeof value === "string" ? value : ""; +} + +export function getStringArray(values: FormValues, id: string) { + const value = values[id]; + return Array.isArray(value) + ? value.filter((item): item is string => typeof item === "string") + : []; +} diff --git a/apps/raycast/src/open-settings.tsx b/apps/raycast/src/open-settings.tsx new file mode 100644 index 00000000000..26bb21790a3 --- /dev/null +++ b/apps/raycast/src/open-settings.tsx @@ -0,0 +1,30 @@ +import { Action, ActionPanel, Form } from "@raycast/api"; +import { useId } from "react"; +import { sendCapDeepLink } from "./deeplink"; +import { type FormValues, getString } from "./form"; + +export default function Command() { + const pageId = useId(); + + async function onSubmit(values: FormValues) { + await sendCapDeepLink("settings/open", { + page: getString(values, pageId), + }); + } + + return ( +
+ + + } + > + + + ); +} diff --git a/apps/raycast/src/pause-recording.ts b/apps/raycast/src/pause-recording.ts new file mode 100644 index 00000000000..84548d7d307 --- /dev/null +++ b/apps/raycast/src/pause-recording.ts @@ -0,0 +1,5 @@ +import { sendCapDeepLink } from "./deeplink"; + +export default async function Command() { + await sendCapDeepLink("record/pause"); +} diff --git a/apps/raycast/src/restart-recording.ts b/apps/raycast/src/restart-recording.ts new file mode 100644 index 00000000000..baabd58d31c --- /dev/null +++ b/apps/raycast/src/restart-recording.ts @@ -0,0 +1,5 @@ +import { sendCapDeepLink } from "./deeplink"; + +export default async function Command() { + await sendCapDeepLink("record/restart"); +} diff --git a/apps/raycast/src/resume-recording.ts b/apps/raycast/src/resume-recording.ts new file mode 100644 index 00000000000..e944c4618d4 --- /dev/null +++ b/apps/raycast/src/resume-recording.ts @@ -0,0 +1,5 @@ +import { sendCapDeepLink } from "./deeplink"; + +export default async function Command() { + await sendCapDeepLink("record/resume"); +} diff --git a/apps/raycast/src/start-recording.tsx b/apps/raycast/src/start-recording.tsx new file mode 100644 index 00000000000..90a9b74e7e9 --- /dev/null +++ b/apps/raycast/src/start-recording.tsx @@ -0,0 +1,136 @@ +import { Action, ActionPanel, Form, showToast, Toast } from "@raycast/api"; +import { useId, useState } from "react"; +import { sendCapDeepLink } from "./deeplink"; +import { type FormValues, getString, getStringArray } from "./form"; + +export default function Command() { + const [cameraSource, setCameraSource] = useState("keep"); + const modeId = useId(); + const captureTypeId = useId(); + const targetId = useId(); + const captureSystemAudioId = useId(); + const micLabelId = useId(); + const cameraSourceId = useId(); + const cameraDeviceId = useId(); + const cameraModelId = useId(); + + async function onSubmit(values: FormValues) { + const target = getString(values, targetId).trim(); + + if (!target) { + await showToast({ + style: Toast.Style.Failure, + title: "Target is required", + message: "Use the exact screen or window name shown in Cap.", + }); + return; + } + + const selectedCameraSource = getString(values, cameraSourceId); + const params: Record = { + mode: getString(values, modeId), + capture_type: getString(values, captureTypeId), + target, + capture_system_audio: getStringArray( + values, + captureSystemAudioId, + ).includes("enabled") + ? "true" + : "false", + mic_label: getString(values, micLabelId), + }; + + if (selectedCameraSource === "device_id") { + const deviceId = getString(values, cameraDeviceId).trim(); + if (!deviceId) { + await showToast({ + style: Toast.Style.Failure, + title: "Camera device ID is required", + }); + return; + } + params.device_id = deviceId; + } + + if (selectedCameraSource === "model_id") { + const modelId = getString(values, cameraModelId).trim(); + if (!modelId) { + await showToast({ + style: Toast.Style.Failure, + title: "Camera model ID is required", + message: "Use the VID:PID format expected by Cap.", + }); + return; + } + params.model_id = modelId; + } + + if (selectedCameraSource === "off") { + params.off = "true"; + } + + await sendCapDeepLink("record/start", params); + } + + return ( +
+ + + } + > + + + + + + + + + + + + + + + + + + + + {cameraSource === "device_id" ? ( + + ) : null} + {cameraSource === "model_id" ? ( + + ) : null} + + ); +} diff --git a/apps/raycast/src/stop-recording.ts b/apps/raycast/src/stop-recording.ts new file mode 100644 index 00000000000..d2ccf1b013b --- /dev/null +++ b/apps/raycast/src/stop-recording.ts @@ -0,0 +1,5 @@ +import { sendCapDeepLink } from "./deeplink"; + +export default async function Command() { + await sendCapDeepLink("record/stop"); +} diff --git a/apps/raycast/src/switch-camera.tsx b/apps/raycast/src/switch-camera.tsx new file mode 100644 index 00000000000..0f93dc4c2fa --- /dev/null +++ b/apps/raycast/src/switch-camera.tsx @@ -0,0 +1,83 @@ +import { Action, ActionPanel, Form, showToast, Toast } from "@raycast/api"; +import { useId, useState } from "react"; +import { sendCapDeepLink } from "./deeplink"; +import { type FormValues, getString } from "./form"; + +export default function Command() { + const [cameraSource, setCameraSource] = useState("off"); + const cameraSourceId = useId(); + const cameraDeviceId = useId(); + const cameraModelId = useId(); + + async function onSubmit(values: FormValues) { + const selectedCameraSource = getString(values, cameraSourceId); + const params: Record = {}; + + if (selectedCameraSource === "device_id") { + const deviceId = getString(values, cameraDeviceId).trim(); + if (!deviceId) { + await showToast({ + style: Toast.Style.Failure, + title: "Camera device ID is required", + }); + return; + } + params.device_id = deviceId; + } + + if (selectedCameraSource === "model_id") { + const modelId = getString(values, cameraModelId).trim(); + if (!modelId) { + await showToast({ + style: Toast.Style.Failure, + title: "Camera model ID is required", + message: "Use the VID:PID format expected by Cap.", + }); + return; + } + params.model_id = modelId; + } + + if (selectedCameraSource === "off") { + params.off = "true"; + } + + await sendCapDeepLink("device/camera", params); + } + + return ( +
+ + + } + > + + + + + + {cameraSource === "device_id" ? ( + + ) : null} + {cameraSource === "model_id" ? ( + + ) : null} + + ); +} diff --git a/apps/raycast/src/switch-microphone.tsx b/apps/raycast/src/switch-microphone.tsx new file mode 100644 index 00000000000..6f82cdad2d2 --- /dev/null +++ b/apps/raycast/src/switch-microphone.tsx @@ -0,0 +1,33 @@ +import { Action, ActionPanel, Form } from "@raycast/api"; +import { useId } from "react"; +import { sendCapDeepLink } from "./deeplink"; +import { type FormValues, getString } from "./form"; + +export default function Command() { + const labelId = useId(); + + async function onSubmit(values: FormValues) { + const label = getString(values, labelId).trim(); + await sendCapDeepLink( + "device/microphone", + label ? { label } : { off: "true" }, + ); + } + + return ( +
+ + + } + > + + + + ); +} diff --git a/apps/raycast/src/toggle-pause-recording.ts b/apps/raycast/src/toggle-pause-recording.ts new file mode 100644 index 00000000000..6ad20f3a31a --- /dev/null +++ b/apps/raycast/src/toggle-pause-recording.ts @@ -0,0 +1,5 @@ +import { sendCapDeepLink } from "./deeplink"; + +export default async function Command() { + await sendCapDeepLink("record/toggle-pause"); +} diff --git a/apps/raycast/tsconfig.json b/apps/raycast/tsconfig.json new file mode 100644 index 00000000000..5203fd5a41e --- /dev/null +++ b/apps/raycast/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": ["node", "react"] + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b165a23f750..5caa994e807 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -297,7 +297,7 @@ importers: version: 4.3.0 '@tailwindcss/typography': specifier: ^0.5.9 - version: 0.5.16(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.15.5(@swc/helpers@0.5.17))(@swc/wasm@1.15.5)(@types/node@20.17.43)(typescript@5.8.3))) + version: 0.5.16(tailwindcss@4.3.0) '@tauri-apps/cli': specifier: '>=2.1.0' version: 2.11.0 @@ -406,6 +406,31 @@ importers: specifier: latest version: 1.3.14 + apps/raycast: + dependencies: + '@raycast/api': + specifier: ^1.95.0 + version: 1.104.19(@types/node@22.13.10)(@types/react@19.0.10) + react: + specifier: 19.0.0 + version: 19.0.0 + devDependencies: + '@raycast/eslint-config': + specifier: ^2.0.2 + version: 2.1.1(eslint@8.57.1)(prettier@3.7.4)(typescript@5.8.3) + '@types/node': + specifier: 22.13.10 + version: 22.13.10 + '@types/react': + specifier: 19.0.10 + version: 19.0.10 + eslint: + specifier: ^8.57.0 + version: 8.57.1 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + apps/storybook: dependencies: '@cap/ui-solid': @@ -2502,9 +2527,6 @@ packages: '@effect/rpc': ^0.71.0 effect: ^3.18.1 - '@emnapi/core@1.11.0': - resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} - '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} @@ -2514,9 +2536,6 @@ packages: '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.11.0': - resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} - '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} @@ -3770,6 +3789,10 @@ packages: resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4156,6 +4179,140 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@internationalized/date@3.8.0': resolution: {integrity: sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==} @@ -4741,14 +4898,26 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true + '@oclif/core@4.11.9': + resolution: {integrity: sha512-w8uupjkdEQQcTFp1BUzWZCRNRW4BwXaSnFldHuziqx79F8q4NRCFwgGoMHV48HpJ6Xn9zeN0l3BiuppnPh6bWA==} + engines: {node: '>=18.0.0'} + '@oclif/core@4.8.1': resolution: {integrity: sha512-07mq0vKCWNsB85ZHeBMlTAiO0KLFqHyAeRK3bD2K8CI1tX3tiwkWw1lZQZkiw8MUBrhxdROhMkYMY4Q0l7JHqA==} engines: {node: '>=18.0.0'} + '@oclif/plugin-autocomplete@3.2.51': + resolution: {integrity: sha512-e8uS47+mpbFum8/QPXOomkPnD3ZBuxkfM/+0FiqK+h1P0dX1ObxdpjXigfopkwEWI5O1W9kmQe3g13FEI4tOgw==} + engines: {node: '>=18.0.0'} + '@oclif/plugin-help@6.2.37': resolution: {integrity: sha512-5N/X/FzlJaYfpaHwDC0YHzOzKDWa41s9t+4FpCDu4f9OMReds4JeNBaaWk9rlIzdKjh2M6AC5Q18ORfECRkHGA==} engines: {node: '>=18.0.0'} + '@oclif/plugin-not-found@3.2.88': + resolution: {integrity: sha512-5YTKSpuV77910/iGnGsj0fr9kOazCy/h1brki1CocbfawdvaVPedcwCH25wMcdRfo8mFD656bG9/kdki/+Wb9A==} + engines: {node: '>=18.0.0'} + '@octokit/app@15.1.6': resolution: {integrity: sha512-WELCamoCJo9SN0lf3SWZccf68CF0sBNPQuLYmZ/n87p5qvBJDe9aBtr5dHkh7T9nxWZ608pizwsUbypSzZAiUw==} engines: {node: '>= 18'} @@ -5907,97 +6076,141 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@raycast/api@1.104.19': + resolution: {integrity: sha512-SAVg56BAzxZGy/OPQ0jekUG3pJaoX5pCqleALvFo9JRE7P2tvKoglWnYRcIJArRhLlvV8FtFNMDafd+NNwXXCw==} + engines: {node: '>=22.22.2'} + hasBin: true + peerDependencies: + '@types/node': 22.19.17 + '@types/react': 19.0.10 + react-devtools: 6.1.1 + peerDependenciesMeta: + '@types/node': + optional: true + '@types/react': + optional: true + react-devtools: + optional: true + + '@raycast/eslint-config@2.1.1': + resolution: {integrity: sha512-W0kxF+FJ+BYQn0EKIV739j2ZrHEtjo/LclsoZgUWg3t364Dq75XKcjqYFYx+59/DBaamY0amdajlfuDAf6veAg==} + peerDependencies: + eslint: '>=8.23.0' + prettier: '>=2' + typescript: '>=4' + + '@raycast/eslint-plugin@2.1.1': + resolution: {integrity: sha512-r2gs8uIlNp6I2mLOyN/kReGlvigzEeuyQPl4yw7nwLy8Zxjfjhg8txMViaBux8juBWBxbSWq/IfW6ZA50oeOHQ==} + peerDependencies: + eslint: '>=8.23.0' + '@react-email/body@0.0.11': resolution: {integrity: sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/button@0.1.0': resolution: {integrity: sha512-fg4LtgTu5zXxaRSly9cuv6sHVF/hi1lElbRaIA8EPx5coWOBhCto6rCPfawcXpaN2oER7rNHUrcNBkI+lz5F9A==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-block@0.1.0': resolution: {integrity: sha512-jSpHFsgqnQXxDIssE4gvmdtFncaFQz5D6e22BnVjcCPk/udK+0A9jRwGFEG8JD2si9ZXBmU4WsuqQEczuZn4ww==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-inline@0.0.5': resolution: {integrity: sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/column@0.0.13': resolution: {integrity: sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/components@0.1.0': resolution: {integrity: sha512-Rx0eZk0XuzLKXC5NoMm8xuH72ALVsPYNb/BvcdCJx4EZAoVpQISb4sCqpo9blVYVIazNr4MqWroqFb3ZNrCLMQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/container@0.0.15': resolution: {integrity: sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/font@0.0.9': resolution: {integrity: sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/head@0.0.12': resolution: {integrity: sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/heading@0.0.15': resolution: {integrity: sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/hr@0.0.11': resolution: {integrity: sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/html@0.0.11': resolution: {integrity: sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/img@0.0.11': resolution: {integrity: sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/link@0.0.12': resolution: {integrity: sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/markdown@0.0.15': resolution: {integrity: sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/preview@0.0.13': resolution: {integrity: sha512-F7j9FJ0JN/A4d7yr+aw28p4uX7VLWs7hTHtLo7WRyw4G+Lit6Zucq4UWKRxJC8lpsUdzVmG7aBJnKOT+urqs/w==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -6011,24 +6224,28 @@ packages: '@react-email/row@0.0.12': resolution: {integrity: sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/section@0.0.16': resolution: {integrity: sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/tailwind@1.0.5': resolution: {integrity: sha512-BH00cZSeFfP9HiDASl+sPHi7Hh77W5nzDgdnxtsVr/m3uQD9g180UwxcE3PhOfx0vRdLzQUU8PtmvvDfbztKQg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/text@0.1.5': resolution: {integrity: sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -8390,6 +8607,9 @@ packages: '@types/node@20.19.21': resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==} + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + '@types/node@22.15.14': resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==} @@ -8425,6 +8645,9 @@ packages: resolution: {integrity: sha512-UzjzmgY/VH3Str6DcAGTLMA1mVVhGOyARNTANExrirtp+JgxhaIOVDxq4TIRmpSi4voLv+w4HA9CC5GvhhCA0A==} deprecated: This is a stub types definition. react-tooltip provides its own type definitions, so you do not need this installed. + '@types/react@19.0.10': + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} + '@types/react@19.1.13': resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==} @@ -8950,6 +9173,7 @@ packages: '@workflow/core@4.2.0-beta.73': resolution: {integrity: sha512-F0DQrw11KO1tG5gIOk493Y+O3OwmI2K31z3/HehCD/HSh7LIgj7gQVwNR78dbkt5+4q3BL8RurRDN35hRqk1tg==} + deprecated: 'Deprecated: this Workflow 4.x beta release is no longer supported. Please upgrade to the latest supported Workflow release.' peerDependencies: '@opentelemetry/api': '1' peerDependenciesMeta: @@ -9020,6 +9244,7 @@ packages: '@workflow/world-vercel@4.1.0-beta.45': resolution: {integrity: sha512-+2u8BtWnOWgdry22zJj9QSgh4s1WrTy9EvAJKx+sFnG24mrVBAW83IvqxpDIXAAd72PTFnEXyGHP4LUIO2KJag==} + deprecated: 'Deprecated: this Workflow 4.x beta release is no longer supported. Please upgrade to the latest supported Workflow release.' peerDependencies: '@opentelemetry/api': '1' peerDependenciesMeta: @@ -9425,6 +9650,7 @@ packages: aws-sdk@2.1692.0: resolution: {integrity: sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==} engines: {node: '>= 10.0.0'} + deprecated: The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil aws-ssl-profiles@1.1.2: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} @@ -9759,6 +9985,9 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -9831,6 +10060,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -10889,6 +11122,12 @@ packages: typescript: optional: true + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-config-prettier@8.10.0: resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} hasBin: true @@ -11280,6 +11519,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-levenshtein@3.0.0: + resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} @@ -11301,6 +11543,10 @@ packages: resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} hasBin: true + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -13122,6 +13368,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -13271,6 +13521,10 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + mux-embed@5.9.0: resolution: {integrity: sha512-wmunL3uoPhma/tWy8PrDPZkvJpXvSFBwbD3KkC4PG8Ztjfb1X3hRJwGUAQyRz7z99b/ovLm2UTTitrkvStjH4w==} @@ -13897,6 +14151,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -14401,6 +14659,10 @@ packages: react: '>=16.14.0' react-dom: '>=16.14.0' + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + react@19.1.1: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} @@ -14798,6 +15060,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -15516,6 +15783,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -15650,6 +15921,7 @@ packages: tsconfck@3.1.5: resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==} engines: {node: ^18 || >=20} + deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -15879,6 +16151,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -16666,6 +16941,7 @@ packages: workflow@4.2.0-beta.73: resolution: {integrity: sha512-qojoyPIommGOVCj/LHOZEBRJqlSbijLOKDr5yzBokhc7qO2wJAXixVshPH6mOxHJc22KuYKFMlJu+ocqhDKT0A==} + deprecated: 'Deprecated: this Workflow 4.x beta release is no longer supported. Please upgrade to the latest supported Workflow release.' hasBin: true peerDependencies: '@opentelemetry/api': '1' @@ -16693,6 +16969,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -16842,6 +17122,10 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + yoctocolors@2.1.1: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} @@ -18694,12 +18978,6 @@ snapshots: '@effect/rpc': 0.71.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4) effect: 3.18.4 - '@emnapi/core@1.11.0': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -18717,11 +18995,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.0': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 @@ -19359,6 +19632,11 @@ snapshots: eslint: 9.30.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.30.1(jiti@2.6.1))': dependencies: eslint: 9.30.1(jiti@2.6.1) @@ -19432,6 +19710,8 @@ snapshots: '@eslint/js@9.30.1': {} + '@eslint/js@9.39.4': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.3.3': @@ -19747,6 +20027,131 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@22.13.10)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.13.10) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/confirm@5.1.21(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/core@10.3.2(@types/node@22.13.10)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.13.10) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/editor@4.2.23(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/external-editor': 1.0.3(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/expand@4.0.23(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/external-editor@1.0.3(@types/node@22.13.10)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.0 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/number@3.0.23(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/password@4.0.23(@types/node@22.13.10)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/prompts@7.10.1(@types/node@22.13.10)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.13.10) + '@inquirer/confirm': 5.1.21(@types/node@22.13.10) + '@inquirer/editor': 4.2.23(@types/node@22.13.10) + '@inquirer/expand': 4.0.23(@types/node@22.13.10) + '@inquirer/input': 4.3.1(@types/node@22.13.10) + '@inquirer/number': 3.0.23(@types/node@22.13.10) + '@inquirer/password': 4.0.23(@types/node@22.13.10) + '@inquirer/rawlist': 4.1.11(@types/node@22.13.10) + '@inquirer/search': 3.2.2(@types/node@22.13.10) + '@inquirer/select': 4.4.2(@types/node@22.13.10) + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/rawlist@4.1.11(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/type': 3.0.10(@types/node@22.13.10) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/search@3.2.2(@types/node@22.13.10)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.13.10) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/select@4.4.2(@types/node@22.13.10)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.13.10) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.13.10) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.13.10 + + '@inquirer/type@3.0.10(@types/node@22.13.10)': + optionalDependencies: + '@types/node': 22.13.10 + '@internationalized/date@3.8.0': dependencies: '@swc/helpers': 0.5.17 @@ -20103,8 +20508,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.9': dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 '@tybys/wasm-util': 0.9.0 optional: true @@ -20483,6 +20888,27 @@ snapshots: dependencies: consola: 3.4.2 + '@oclif/core@4.11.9': + dependencies: + ansi-escapes: 4.3.2 + ansis: 3.17.0 + clean-stack: 3.0.1 + cli-spinners: 2.9.2 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + get-package-type: 0.1.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + lilconfig: 3.1.3 + minimatch: 10.2.5 + semver: 7.8.5 + string-width: 4.2.3 + supports-color: 8.1.1 + tinyglobby: 0.2.17 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + '@oclif/core@4.8.1': dependencies: ansi-escapes: 4.3.2 @@ -20504,9 +20930,27 @@ snapshots: wordwrap: 1.0.0 wrap-ansi: 7.0.0 + '@oclif/plugin-autocomplete@3.2.51': + dependencies: + '@oclif/core': 4.11.9 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + transitivePeerDependencies: + - supports-color + '@oclif/plugin-help@6.2.37': dependencies: - '@oclif/core': 4.8.1 + '@oclif/core': 4.11.9 + + '@oclif/plugin-not-found@3.2.88(@types/node@22.13.10)': + dependencies: + '@inquirer/prompts': 7.10.1(@types/node@22.13.10) + '@oclif/core': 4.11.9 + ansis: 3.17.0 + fast-levenshtein: 3.0.0 + transitivePeerDependencies: + - '@types/node' '@octokit/app@15.1.6': dependencies: @@ -21834,6 +22278,41 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@raycast/api@1.104.19(@types/node@22.13.10)(@types/react@19.0.10)': + dependencies: + '@oclif/core': 4.11.9 + '@oclif/plugin-autocomplete': 3.2.51 + '@oclif/plugin-help': 6.2.37 + '@oclif/plugin-not-found': 3.2.88(@types/node@22.13.10) + esbuild: 0.27.4 + react: 19.0.0 + optionalDependencies: + '@types/node': 22.13.10 + '@types/react': 19.0.10 + transitivePeerDependencies: + - supports-color + + '@raycast/eslint-config@2.1.1(eslint@8.57.1)(prettier@3.7.4)(typescript@5.8.3)': + dependencies: + '@eslint/js': 9.39.4 + '@raycast/eslint-plugin': 2.1.1(eslint@8.57.1)(typescript@5.8.3) + eslint: 8.57.1 + eslint-config-prettier: 10.1.8(eslint@8.57.1) + globals: 16.4.0 + prettier: 3.7.4 + typescript: 5.8.3 + typescript-eslint: 8.57.2(eslint@8.57.1)(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + + '@raycast/eslint-plugin@2.1.1(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + '@react-email/body@0.0.11(react@19.1.1)': dependencies: react: 19.1.1 @@ -24677,7 +25156,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 20.17.43 + '@types/node': 20.19.21 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -24724,7 +25203,7 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.17.43 + '@types/node': 20.19.21 form-data: 4.0.2 '@types/node@10.17.60': {} @@ -24743,6 +25222,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 + '@types/node@22.15.14': dependencies: undici-types: 6.21.0 @@ -24779,6 +25262,10 @@ snapshots: - react - react-dom + '@types/react@19.0.10': + dependencies: + csstype: 3.2.3 + '@types/react@19.1.13': dependencies: csstype: 3.2.3 @@ -24851,6 +25338,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/type-utils': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.57.2 + eslint: 8.57.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -24879,6 +25382,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.2 @@ -24926,6 +25441,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.57.2(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + debug: 4.4.3(supports-color@8.1.1) + eslint: 8.57.1 + ts-api-utils: 2.5.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.57.2 @@ -24986,6 +25513,17 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@8.57.2(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + eslint: 8.57.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.30.1(jiti@2.6.1)) @@ -25361,7 +25899,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.43)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(terser@5.44.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.17)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(terser@5.44.0)(yaml@2.8.1) '@vitest/utils@2.0.5': dependencies: @@ -26667,6 +27205,8 @@ snapshots: character-reference-invalid@2.0.1: {} + chardet@2.2.0: {} + check-error@2.1.1: {} chokidar@3.6.0: @@ -26724,6 +27264,8 @@ snapshots: cli-spinners@2.9.2: {} + cli-width@4.1.0: {} + client-only@0.0.1: {} clipboardy@4.0.0: @@ -27921,7 +28463,7 @@ snapshots: '@next/eslint-plugin-next': 16.2.1 eslint: 9.30.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.30.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.6.1)) @@ -27936,6 +28478,10 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-prettier@10.1.8(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-config-prettier@8.10.0(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -27968,7 +28514,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3(supports-color@8.1.1) @@ -27994,14 +28540,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3) eslint: 9.30.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -28045,7 +28591,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.30.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)))(eslint@9.30.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -28609,6 +29155,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-levenshtein@3.0.0: + dependencies: + fastest-levenshtein: 1.0.16 + fast-safe-stringify@2.1.1: {} fast-uri@3.0.6: {} @@ -28631,6 +29181,8 @@ snapshots: path-expression-matcher: 1.2.0 strnum: 2.2.2 + fastest-levenshtein@1.0.16: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -28651,6 +29203,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + fecha@4.2.3: {} fetch-blob@3.2.0: @@ -30783,6 +31339,10 @@ snapshots: dependencies: brace-expansion: 5.0.5 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -30929,6 +31489,8 @@ snapshots: mustache@4.2.0: {} + mute-stream@2.0.0: {} + mux-embed@5.9.0: {} mysql2@3.15.2: @@ -31755,6 +32317,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.4: {} + pify@2.3.0: {} pirates@4.0.7: {} @@ -32313,6 +32877,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + react@19.0.0: {} + react@19.1.1: {} react@19.2.4: {} @@ -32847,6 +33413,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -33519,7 +34087,7 @@ snapshots: stripe@14.25.0: dependencies: - '@types/node': 20.17.43 + '@types/node': 20.19.21 qs: 6.14.0 strnum@1.1.2: {} @@ -33830,6 +34398,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.0.2: {} tinypool@1.1.1: {} @@ -34161,6 +34734,17 @@ snapshots: typedarray@0.0.6: {} + typescript-eslint@8.57.2(eslint@8.57.1)(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@8.57.1)(typescript@5.8.3) + eslint: 8.57.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + typescript-eslint@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3): dependencies: '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.30.1(jiti@2.6.1))(typescript@5.8.3) @@ -34231,6 +34815,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@6.20.0: {} + undici-types@6.21.0: {} undici@5.28.4: @@ -35394,6 +35980,12 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -35496,6 +36088,8 @@ snapshots: yocto-queue@1.2.1: {} + yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.1: {} youch-core@0.3.2: