Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
383 KiB
JSON

{"repo": "buiilding/windie-sandbox", "files": {"src/local/setup.rs": {"45b92c0cd5ae142b79b7dfbad43fa9b37bcfff42": {"t": 1785745855.547722, "n": 32973, "h": "dda880b76df1f02d", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Desktop Commander\"\n .to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the user-local directory containing the Windie binaries.\n///\n/// A packaged executable knows its own install directory. Prefer that path so\n/// uninstall works even when the installer used a custom directory and did not\n/// persist `WINDIE_INSTALL_DIR` into future shells. The explicit environment\n/// variable remains first for isolated local-release tests and administrators\n/// that intentionally manage a custom layout.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_INSTALL_DIR\") {\n return Ok(PathBuf::from(path));\n }\n\n if let Some(path) = packaged_executable_directory()? {\n return Ok(path);\n }\n\n #[cfg(windows)]\n {\n if let Some(local_app_data) = env::var_os(\"LOCALAPPDATA\") {\n return Ok(PathBuf::from(local_app_data).join(\"Windie\").join(\"bin\"));\n }\n }\n\n Ok(user_home.join(\".local\").join(\"bin\"))\n}\n\n/// Returns the directory of a packaged Windie executable when recognizable.\n///\n/// The release manifest is the ownership marker placed beside all published\n/// binaries. Requiring it prevents a developer binary under `target\\debug` or\n/// `target\\release` from accidentally turning its build directory into an\n/// uninstall target.\nfn packaged_executable_directory() -> Result<Option<PathBuf>> {\n let executable = env::current_exe().ok();\n Ok(executable.and_then(|executable| packaged_executable_directory_for(&executable)))\n}\n\n/// Recognizes one packaged executable path using the adjacent release marker.\nfn packaged_executable_directory_for(executable: &Path) -> Option<PathBuf> {\n let directory = executable.parent()?;\n let name = executable.file_name().and_then(|name| name.to_str())?;\n\n if !name.eq_ignore_ascii_case(&executable_name(\"windie\"))\n || !directory.join(\"release-manifest.txt\").is_file()\n {\n return None;\n }\n\n Some(directory.to_path_buf())\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let path_literals = paths\n .iter()\n .map(|path| powershell_single_quoted(path.to_string_lossy().as_ref()))\n .collect::<Vec<_>>()\n .join(\", \");\n let script = format!(\n \"Start-Sleep -Milliseconds 500; $paths = @({path_literals}); foreach ($path in $paths) {{ if (Test-Path -LiteralPath $path) {{ Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue }} }}\"\n );\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n &script,\n ]);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(windows)]\nfn powershell_single_quoted(value: &str) -> String {\n format!(\"'{}'\", value.replace('\\'', \"''\"))\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn packaged_executable_requires_release_manifest() {\n let root = std::env::temp_dir().join(format!(\n \"windie-packaged-install-test-{}\",\n std::process::id()\n ));\n let executable = root.join(executable_name(\"windie\"));\n fs::create_dir_all(&root).unwrap();\n fs::write(&executable, \"owned\").unwrap();\n\n assert_eq!(packaged_executable_directory_for(&executable), None);\n\n fs::write(root.join(\"release-manifest.txt\"), \"version=local\\n\").unwrap();\n assert_eq!(\n packaged_executable_directory_for(&executable),\n Some(root.clone())\n );\n\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "924f659d90f5725c44ce1f79b948c7f92ca34d0f": {"t": 1785757299.2585204, "n": 32973, "h": "dda880b76df1f02d", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Desktop Commander\"\n .to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the user-local directory containing the Windie binaries.\n///\n/// A packaged executable knows its own install directory. Prefer that path so\n/// uninstall works even when the installer used a custom directory and did not\n/// persist `WINDIE_INSTALL_DIR` into future shells. The explicit environment\n/// variable remains first for isolated local-release tests and administrators\n/// that intentionally manage a custom layout.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_INSTALL_DIR\") {\n return Ok(PathBuf::from(path));\n }\n\n if let Some(path) = packaged_executable_directory()? {\n return Ok(path);\n }\n\n #[cfg(windows)]\n {\n if let Some(local_app_data) = env::var_os(\"LOCALAPPDATA\") {\n return Ok(PathBuf::from(local_app_data).join(\"Windie\").join(\"bin\"));\n }\n }\n\n Ok(user_home.join(\".local\").join(\"bin\"))\n}\n\n/// Returns the directory of a packaged Windie executable when recognizable.\n///\n/// The release manifest is the ownership marker placed beside all published\n/// binaries. Requiring it prevents a developer binary under `target\\debug` or\n/// `target\\release` from accidentally turning its build directory into an\n/// uninstall target.\nfn packaged_executable_directory() -> Result<Option<PathBuf>> {\n let executable = env::current_exe().ok();\n Ok(executable.and_then(|executable| packaged_executable_directory_for(&executable)))\n}\n\n/// Recognizes one packaged executable path using the adjacent release marker.\nfn packaged_executable_directory_for(executable: &Path) -> Option<PathBuf> {\n let directory = executable.parent()?;\n let name = executable.file_name().and_then(|name| name.to_str())?;\n\n if !name.eq_ignore_ascii_case(&executable_name(\"windie\"))\n || !directory.join(\"release-manifest.txt\").is_file()\n {\n return None;\n }\n\n Some(directory.to_path_buf())\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let path_literals = paths\n .iter()\n .map(|path| powershell_single_quoted(path.to_string_lossy().as_ref()))\n .collect::<Vec<_>>()\n .join(\", \");\n let script = format!(\n \"Start-Sleep -Milliseconds 500; $paths = @({path_literals}); foreach ($path in $paths) {{ if (Test-Path -LiteralPath $path) {{ Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue }} }}\"\n );\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n &script,\n ]);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(windows)]\nfn powershell_single_quoted(value: &str) -> String {\n format!(\"'{}'\", value.replace('\\'', \"''\"))\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn packaged_executable_requires_release_manifest() {\n let root = std::env::temp_dir().join(format!(\n \"windie-packaged-install-test-{}\",\n std::process::id()\n ));\n let executable = root.join(executable_name(\"windie\"));\n fs::create_dir_all(&root).unwrap();\n fs::write(&executable, \"owned\").unwrap();\n\n assert_eq!(packaged_executable_directory_for(&executable), None);\n\n fs::write(root.join(\"release-manifest.txt\"), \"version=local\\n\").unwrap();\n assert_eq!(\n packaged_executable_directory_for(&executable),\n Some(root.clone())\n );\n\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "b0bdca571f74ec4d6686ff190167ae75373ca5b7": {"t": 1785757300.683289, "n": 33051, "h": "f40ab5142e4f4157", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Desktop Commander\"\n .to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the user-local directory containing the Windie binaries.\n///\n/// A packaged executable knows its own install directory. Prefer that path so\n/// uninstall works even when the installer used a custom directory and did not\n/// persist `WINDIE_INSTALL_DIR` into future shells. The explicit environment\n/// variable remains first for isolated local-release tests and administrators\n/// that intentionally manage a custom layout.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_INSTALL_DIR\") {\n return Ok(PathBuf::from(path));\n }\n\n if let Some(path) = packaged_executable_directory()? {\n return Ok(path);\n }\n\n #[cfg(windows)]\n {\n if let Some(local_app_data) = env::var_os(\"LOCALAPPDATA\") {\n return Ok(PathBuf::from(local_app_data).join(\"Windie\").join(\"bin\"));\n }\n }\n\n Ok(user_home.join(\".local\").join(\"bin\"))\n}\n\n/// Returns the directory of a packaged Windie executable when recognizable.\n///\n/// The release manifest is the ownership marker placed beside all published\n/// binaries. Requiring it prevents a developer binary under `target\\debug` or\n/// `target\\release` from accidentally turning its build directory into an\n/// uninstall target.\nfn packaged_executable_directory() -> Result<Option<PathBuf>> {\n let executable = env::current_exe().ok();\n Ok(executable.and_then(|executable| packaged_executable_directory_for(&executable)))\n}\n\n/// Recognizes one packaged executable path using the adjacent release marker.\nfn packaged_executable_directory_for(executable: &Path) -> Option<PathBuf> {\n let Some(directory) = executable.parent() else {\n return None;\n };\n let Some(name) = executable.file_name().and_then(|name| name.to_str()) else {\n return None;\n };\n\n if !name.eq_ignore_ascii_case(&executable_name(\"windie\"))\n || !directory.join(\"release-manifest.txt\").is_file()\n {\n return None;\n }\n\n Some(directory.to_path_buf())\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let path_literals = paths\n .iter()\n .map(|path| powershell_single_quoted(path.to_string_lossy().as_ref()))\n .collect::<Vec<_>>()\n .join(\", \");\n let script = format!(\n \"Start-Sleep -Milliseconds 500; $paths = @({path_literals}); foreach ($path in $paths) {{ if (Test-Path -LiteralPath $path) {{ Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue }} }}\"\n );\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n &script,\n ]);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(windows)]\nfn powershell_single_quoted(value: &str) -> String {\n format!(\"'{}'\", value.replace('\\'', \"''\"))\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn packaged_executable_requires_release_manifest() {\n let root = std::env::temp_dir().join(format!(\n \"windie-packaged-install-test-{}\",\n std::process::id()\n ));\n let executable = root.join(executable_name(\"windie\"));\n fs::create_dir_all(&root).unwrap();\n fs::write(&executable, \"owned\").unwrap();\n\n assert_eq!(packaged_executable_directory_for(&executable), None);\n\n fs::write(root.join(\"release-manifest.txt\"), \"version=local\\n\").unwrap();\n assert_eq!(\n packaged_executable_directory_for(&executable),\n Some(root.clone())\n );\n\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "e421350b361802abda5472c8b7c85543c42fba09": {"t": 1785757302.3608234, "n": 30612, "h": "381e33f46b8780da", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Desktop Commander\"\n .to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let path_literals = paths\n .iter()\n .map(|path| powershell_single_quoted(path.to_string_lossy().as_ref()))\n .collect::<Vec<_>>()\n .join(\", \");\n let script = format!(\n \"Start-Sleep -Milliseconds 500; $paths = @({path_literals}); foreach ($path in $paths) {{ if (Test-Path -LiteralPath $path) {{ Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue }} }}\"\n );\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n &script,\n ]);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(windows)]\nfn powershell_single_quoted(value: &str) -> String {\n format!(\"'{}'\", value.replace('\\'', \"''\"))\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "ba523fb652d18123777d644ad80db2990b074816": {"t": 1785757304.3557673, "n": 30299, "h": "508bfc7867dd8986", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Desktop Commander\"\n .to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Windie-managed uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let script = \"Start-Sleep -Milliseconds 500; foreach ($path in $args) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } }\";\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n script,\n \"--\",\n ]);\n command.args(paths);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "332872a9deaa4f80c724ae07848a62628abea138": {"t": 1785757306.7236931, "n": 30218, "h": "76d168c889d98747", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let script = \"Start-Sleep -Milliseconds 500; foreach ($path in $args) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } }\";\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n script,\n \"--\",\n ]);\n command.args(paths);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "1a2bf7251ef793c032852d4e21ffd427672257b9": {"t": 1785757308.123701, "n": 28488, "h": "4cbf8cc4376219bf", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n })\n }\n \"blender-mcp\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n })\n }\n \"brightdata\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n })\n }\n \"basic-memory\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let script = \"Start-Sleep -Milliseconds 500; foreach ($path in $args) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } }\";\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n script,\n \"--\",\n ]);\n command.args(paths);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n });\n }\n\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "7dcba82b6288e48b5f570e628d25ba264a5c8e26": {"t": 1785757309.8921218, "n": 28394, "h": "fae3b6e68f5a3b75", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n })\n }\n \"blender-mcp\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n })\n }\n \"brightdata\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n })\n }\n \"basic-memory\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let script = \"Start-Sleep -Milliseconds 500; foreach ($path in $args) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } }\";\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n script,\n \"--\",\n ]);\n command.args(paths);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n });\n }\n\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(install_dir.join(\"windie\"), \"owned\").unwrap();\n fs::write(install_dir.join(\"unrelated\"), \"preserve\").unwrap();\n\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries: vec![\n install_dir.join(\"windie\"),\n install_dir.join(\"bifrost\"),\n install_dir.join(\"windie-inspector\"),\n ],\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!install_dir.join(\"windie\").exists());\n assert!(install_dir.join(\"unrelated\").exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "32bbffc84c98afd98838b550c06c855cb6ee102d": {"t": 1785757311.386018, "n": 17512, "h": "8090123d80f02cdc", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n })\n }\n \"blender-mcp\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n })\n }\n \"brightdata\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n })\n }\n \"basic-memory\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n });\n }\n\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "6a94e3832c7880fadd32ced7658e770f4bc92210": {"t": 1785757313.414561, "n": 17589, "h": "59c43fb787f657b2", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\nuse uuid::Uuid;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst API_TOKEN_FILE_NAME: &str = \"api-token\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub api_token_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub inspector_log_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the stable localhost API token shared by `windie api` and UI clients.\npub fn ensure_api_token() -> Result<String> {\n let layout = ensure_windie_layout()?;\n if layout.api_token_file.exists() {\n let token = fs::read_to_string(&layout.api_token_file)\n .with_context(|| format!(\"failed to read {}\", layout.api_token_file.display()))?\n .trim()\n .to_string();\n if !token.is_empty() {\n return Ok(token);\n }\n }\n\n let token = Uuid::new_v4().to_string();\n write_secret_file(&layout.api_token_file, &format!(\"{token}\\n\"))?;\n\n Ok(token)\n}\n\n/// Returns the log file used by the detached local inspector dev server.\npub fn inspector_log_file_path() -> Result<PathBuf> {\n Ok(ensure_windie_layout()?.inspector_log_file)\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Returns whether a key belongs to an LLM provider and therefore must be\n/// managed by Bifrost instead of Windie's MCP environment file.\npub fn is_llm_env_key(key: &str) -> bool {\n LLM_ENV_KEYS.contains(&key)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n })\n }\n \"blender-mcp\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n })\n }\n \"brightdata\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n })\n }\n \"basic-memory\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n api_token_file: root.join(API_TOKEN_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n });\n }\n\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n/// Writes a user-local secret file without inheriting permissive default modes.\nfn write_secret_file(path: &Path, text: &str) -> Result<()> {\n let mut options = fs::OpenOptions::new();\n options.write(true).create(true).truncate(true);\n #[cfg(unix)]\n {\n use std::os::unix::fs::OpenOptionsExt;\n options.mode(0o600);\n }\n\n let mut file = options\n .open(path)\n .with_context(|| format!(\"failed to write {}\", path.display()))?;\n file.write_all(text.as_bytes())\n .with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "e41df32692c02ff88b4f497e317e2c2514cc2726": {"t": 1785757314.921588, "n": 17471, "h": "1f3d55cb381412f5", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\nuse uuid::Uuid;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst API_TOKEN_FILE_NAME: &str = \"api-token\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub api_token_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub inspector_log_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the stable localhost API token shared by `windie api` and UI clients.\npub fn ensure_api_token() -> Result<String> {\n let layout = ensure_windie_layout()?;\n if layout.api_token_file.exists() {\n let token = fs::read_to_string(&layout.api_token_file)\n .with_context(|| format!(\"failed to read {}\", layout.api_token_file.display()))?\n .trim()\n .to_string();\n if !token.is_empty() {\n return Ok(token);\n }\n }\n\n let token = Uuid::new_v4().to_string();\n write_secret_file(&layout.api_token_file, &format!(\"{token}\\n\"))?;\n\n Ok(token)\n}\n\n/// Returns the log file used by the detached local inspector dev server.\npub fn inspector_log_file_path() -> Result<PathBuf> {\n Ok(ensure_windie_layout()?.inspector_log_file)\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Returns whether a key belongs to an LLM provider and therefore must be\n/// managed by Bifrost instead of Windie's MCP environment file.\npub fn is_llm_env_key(key: &str) -> bool {\n LLM_ENV_KEYS.contains(&key)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n })\n }\n \"blender-mcp\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n })\n }\n \"brightdata\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n })\n }\n \"basic-memory\" => {\n runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n api_token_file: root.join(API_TOKEN_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n });\n }\n\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path -AutoStart; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n/// Writes a user-local secret file without inheriting permissive default modes.\nfn write_secret_file(path: &Path, text: &str) -> Result<()> {\n let mut options = fs::OpenOptions::new();\n options.write(true).create(true).truncate(true);\n #[cfg(unix)]\n {\n use std::os::unix::fs::OpenOptionsExt;\n options.mode(0o600);\n }\n\n let mut file = options\n .open(path)\n .with_context(|| format!(\"failed to write {}\", path.display()))?;\n file.write_all(text.as_bytes())\n .with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "cbcb55bf7a1a282cb5768d6ee1a491367500bfb0": {"t": 1785757317.068601, "n": 15700, "h": "864c36fb78243846", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\nuse uuid::Uuid;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst API_TOKEN_FILE_NAME: &str = \"api-token\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub api_token_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub inspector_log_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the stable localhost API token shared by `windie api` and UI clients.\npub fn ensure_api_token() -> Result<String> {\n let layout = ensure_windie_layout()?;\n if layout.api_token_file.exists() {\n let token = fs::read_to_string(&layout.api_token_file)\n .with_context(|| format!(\"failed to read {}\", layout.api_token_file.display()))?\n .trim()\n .to_string();\n if !token.is_empty() {\n return Ok(token);\n }\n }\n\n let token = Uuid::new_v4().to_string();\n write_secret_file(&layout.api_token_file, &format!(\"{token}\\n\"))?;\n\n Ok(token)\n}\n\n/// Returns the log file used by the detached local inspector dev server.\npub fn inspector_log_file_path() -> Result<PathBuf> {\n Ok(ensure_windie_layout()?.inspector_log_file)\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Returns whether a key belongs to an LLM provider and therefore must be\n/// managed by Bifrost instead of Windie's MCP environment file.\npub fn is_llm_env_key(key: &str) -> bool {\n LLM_ENV_KEYS.contains(&key)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message:\n \"Desktop Commander will run through public npx package @wonderwhy-er/desktop-commander@latest\"\n .to_string(),\n })\n }\n \"blender-mcp\" => {\n require_command(\"uvx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Blender MCP will run through public uvx package blender-mcp\".to_string(),\n })\n }\n \"brightdata\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Bright Data MCP will run through public npx package @brightdata/mcp\"\n .to_string(),\n })\n }\n \"basic-memory\" => {\n require_command(\"uvx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Basic Memory will run through public uvx package basic-memory\"\n .to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n api_token_file: root.join(API_TOKEN_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| path.join(program).is_file())\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n/// Writes a user-local secret file without inheriting permissive default modes.\nfn write_secret_file(path: &Path, text: &str) -> Result<()> {\n let mut options = fs::OpenOptions::new();\n options.write(true).create(true).truncate(true);\n #[cfg(unix)]\n {\n use std::os::unix::fs::OpenOptionsExt;\n options.mode(0o600);\n }\n\n let mut file = options\n .open(path)\n .with_context(|| format!(\"failed to write {}\", path.display()))?;\n file.write_all(text.as_bytes())\n .with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "71e058d86e4e6b417eaf9649fe950a4e5b4a3d01": {"t": 1785757318.3034742, "n": 14906, "h": "a65924ac11486fb8", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\nuse uuid::Uuid;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst API_TOKEN_FILE_NAME: &str = \"api-token\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub api_token_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub inspector_log_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the stable localhost API token shared by `windie api` and UI clients.\npub fn ensure_api_token() -> Result<String> {\n let layout = ensure_windie_layout()?;\n if layout.api_token_file.exists() {\n let token = fs::read_to_string(&layout.api_token_file)\n .with_context(|| format!(\"failed to read {}\", layout.api_token_file.display()))?\n .trim()\n .to_string();\n if !token.is_empty() {\n return Ok(token);\n }\n }\n\n let token = Uuid::new_v4().to_string();\n write_secret_file(&layout.api_token_file, &format!(\"{token}\\n\"))?;\n\n Ok(token)\n}\n\n/// Returns the log file used by the detached local inspector dev server.\npub fn inspector_log_file_path() -> Result<PathBuf> {\n Ok(ensure_windie_layout()?.inspector_log_file)\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Returns whether a key belongs to an LLM provider and therefore must be\n/// managed by Bifrost instead of Windie's MCP environment file.\npub fn is_llm_env_key(key: &str) -> bool {\n LLM_ENV_KEYS.contains(&key)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message:\n \"Desktop Commander will run through public npx package @wonderwhy-er/desktop-commander@latest\"\n .to_string(),\n })\n }\n \"blender-mcp\" => {\n require_command(\"uvx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Blender MCP will run through public uvx package blender-mcp\".to_string(),\n })\n }\n \"brightdata\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Bright Data MCP will run through public npx package @brightdata/mcp\"\n .to_string(),\n })\n }\n \"basic-memory\" => {\n require_command(\"uvx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Basic Memory will run through public uvx package basic-memory\"\n .to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let Some(home) = env::var_os(\"HOME\") else {\n return Err(anyhow!(\"HOME is not set\"));\n };\n let root = PathBuf::from(home).join(\".windie\");\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n api_token_file: root.join(API_TOKEN_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| path.join(program).is_file())\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n/// Writes a user-local secret file without inheriting permissive default modes.\nfn write_secret_file(path: &Path, text: &str) -> Result<()> {\n let mut options = fs::OpenOptions::new();\n options.write(true).create(true).truncate(true);\n #[cfg(unix)]\n {\n use std::os::unix::fs::OpenOptionsExt;\n options.mode(0o600);\n }\n\n let mut file = options\n .open(path)\n .with_context(|| format!(\"failed to write {}\", path.display()))?;\n file.write_all(text.as_bytes())\n .with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}, "5f3a6795f175f112aab26801afd1b8e4e7bb39b0": {"t": 1785757319.7502832, "n": 13614, "h": "b4ae5162bdaafcbf", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\nuse uuid::Uuid;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst API_TOKEN_FILE_NAME: &str = \"api-token\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub api_token_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub inspector_log_file: PathBuf,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the stable localhost API token shared by `windie api` and UI clients.\npub fn ensure_api_token() -> Result<String> {\n let layout = ensure_windie_layout()?;\n if layout.api_token_file.exists() {\n let token = fs::read_to_string(&layout.api_token_file)\n .with_context(|| format!(\"failed to read {}\", layout.api_token_file.display()))?\n .trim()\n .to_string();\n if !token.is_empty() {\n return Ok(token);\n }\n }\n\n let token = Uuid::new_v4().to_string();\n write_secret_file(&layout.api_token_file, &format!(\"{token}\\n\"))?;\n\n Ok(token)\n}\n\n/// Returns the log file used by the detached local inspector dev server.\npub fn inspector_log_file_path() -> Result<PathBuf> {\n Ok(ensure_windie_layout()?.inspector_log_file)\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost will run through public npx package @maximhq/bifrost\".to_string(),\n })\n }\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message:\n \"Desktop Commander will run through public npx package @wonderwhy-er/desktop-commander@latest\"\n .to_string(),\n })\n }\n \"blender-mcp\" => {\n require_command(\"uvx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Blender MCP will run through public uvx package blender-mcp\".to_string(),\n })\n }\n \"brightdata\" => {\n require_command(\"npx\")?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Bright Data MCP will run through public npx package @brightdata/mcp\"\n .to_string(),\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let Some(home) = env::var_os(\"HOME\") else {\n return Err(anyhow!(\"HOME is not set\"));\n };\n let root = PathBuf::from(home).join(\".windie\");\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n api_token_file: root.join(API_TOKEN_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n root,\n })\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n })\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| path.join(program).is_file())\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n/// Writes a user-local secret file without inheriting permissive default modes.\nfn write_secret_file(path: &Path, text: &str) -> Result<()> {\n let mut options = fs::OpenOptions::new();\n options.write(true).create(true).truncate(true);\n #[cfg(unix)]\n {\n use std::os::unix::fs::OpenOptionsExt;\n options.mode(0o600);\n }\n\n let mut file = options\n .open(path)\n .with_context(|| format!(\"failed to write {}\", path.display()))?;\n file.write_all(text.as_bytes())\n .with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n}\n"}, "4cfb18bd8d7b47b648579b2a711a57342b44b027": {"t": 1786067482.0588253, "n": 30218, "h": "76d168c889d98747", "c": "//! User-local Windie setup, environment, and approved dependency installation.\n//!\n//! This module owns filesystem setup under `~/.windie`, edits Windie's explicit\n//! provider-key environment file, and runs install/check commands for\n//! code-approved runtime dependencies. It deliberately does not configure\n//! arbitrary MCP servers or read project-local `.env` files.\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse anyhow::{Context, Result, anyhow};\n\nuse super::runtime;\n\nconst ENV_FILE_NAME: &str = \".env\";\nconst BIFROST_DIR: &str = \"bifrost\";\nconst BENCHMARK_DIR: &str = \"benchmarks\";\nconst GATEWAY_LOG_FILE_NAME: &str = \"windie-gateway.log\";\nconst GATEWAY_PID_FILE_NAME: &str = \"bifrost.pid\";\nconst API_LOG_FILE_NAME: &str = \"windie-api.log\";\nconst API_PID_FILE_NAME: &str = \"windie-api.pid\";\nconst INSPECTOR_LOG_FILE_NAME: &str = \"windie-inspector.log\";\nconst INSPECTOR_PID_FILE_NAME: &str = \"windie-inspector.pid\";\nconst TRAY_LOG_FILE_NAME: &str = \"windie-tray.log\";\nconst TRAY_PID_FILE_NAME: &str = \"windie-tray.pid\";\nconst LLM_ENV_KEYS: &[&str] = &[\n \"OPENAI_API_KEY\",\n \"OPENROUTER_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"GEMINI_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"COHERE_API_KEY\",\n \"GROQ_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"XAI_API_KEY\",\n \"FIREWORKS_API_KEY\",\n \"TOGETHERAI_API_KEY\",\n \"AZURE_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"AWS_ACCESS_KEY_ID\",\n \"AWS_SECRET_ACCESS_KEY\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n];\nconst CUA_DRIVER_INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh\";\n\n/// Returns the current user's home directory across supported operating\n/// systems. Unix environments conventionally expose `HOME`; native Windows\n/// exposes `USERPROFILE` instead.\npub fn user_home_dir() -> Result<PathBuf> {\n env::var_os(\"HOME\")\n .or_else(|| env::var_os(\"USERPROFILE\"))\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n .ok_or_else(|| anyhow!(\"HOME or USERPROFILE is not set\"))\n}\n\n/// Returns Windie's user-local data directory.\n///\n/// `WINDIE_HOME` is an explicit escape hatch for isolated installations and\n/// tests. Without it, Windie stores state under the platform user's home.\npub fn windie_home_dir() -> Result<PathBuf> {\n if let Some(path) = env::var_os(\"WINDIE_HOME\")\n .map(PathBuf::from)\n .filter(|path| !path.as_os_str().is_empty())\n {\n return Ok(path);\n }\n\n Ok(user_home_dir()?.join(\".windie\"))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Result of one approved installation request.\npub struct InstallReport {\n pub target: String,\n pub message: String,\n pub status: InstallStatus,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n/// Describes whether an approved dependency was reused or installed.\npub enum InstallStatus {\n Detected,\n Installed,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Paths that make up Windie's user-local runtime layout.\npub struct WindieLayout {\n pub root: PathBuf,\n pub env_file: PathBuf,\n pub bifrost_dir: PathBuf,\n pub benchmarks_dir: PathBuf,\n pub gateway_log_file: PathBuf,\n pub gateway_pid_file: PathBuf,\n pub api_log_file: PathBuf,\n pub api_pid_file: PathBuf,\n pub inspector_log_file: PathBuf,\n pub inspector_pid_file: PathBuf,\n pub tray_log_file: PathBuf,\n pub tray_pid_file: PathBuf,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Exact paths that the uninstall operation is allowed to remove.\npub struct UninstallPlan {\n pub windie_home: PathBuf,\n pub install_dir: PathBuf,\n pub binaries: Vec<PathBuf>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Filesystem result from one Windie uninstall attempt.\npub struct UninstallCleanup {\n pub removed_data: bool,\n pub removed_binaries: Vec<PathBuf>,\n pub deferred_binaries: Vec<PathBuf>,\n pub cleanup_scheduled: bool,\n}\n\n/// Creates Windie's required user-local directories and empty env file.\npub fn ensure_windie_layout() -> Result<WindieLayout> {\n let layout = windie_layout()?;\n\n fs::create_dir_all(&layout.root)\n .with_context(|| format!(\"failed to create {}\", layout.root.display()))?;\n fs::create_dir_all(&layout.bifrost_dir)\n .with_context(|| format!(\"failed to create {}\", layout.bifrost_dir.display()))?;\n fs::create_dir_all(&layout.benchmarks_dir)\n .with_context(|| format!(\"failed to create {}\", layout.benchmarks_dir.display()))?;\n if !layout.env_file.exists() {\n fs::write(&layout.env_file, \"\")\n .with_context(|| format!(\"failed to create {}\", layout.env_file.display()))?;\n }\n\n Ok(layout)\n}\n\n/// Returns the only supported Windie provider-key environment file path.\npub fn env_file_path() -> Result<PathBuf> {\n Ok(windie_layout()?.env_file)\n}\n\n/// Returns the persistent log file for one managed component.\npub fn component_log_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_log_file,\n crate::process::ManagedComponent::Api => layout.api_log_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_log_file,\n crate::process::ManagedComponent::Tray => layout.tray_log_file,\n })\n}\n\n/// Returns the persistent PID file for one managed component.\npub fn component_pid_file_path(component: crate::process::ManagedComponent) -> Result<PathBuf> {\n let layout = ensure_windie_layout()?;\n Ok(match component {\n crate::process::ManagedComponent::Gateway => layout.gateway_pid_file,\n crate::process::ManagedComponent::Api => layout.api_pid_file,\n crate::process::ManagedComponent::Inspector => layout.inspector_pid_file,\n crate::process::ManagedComponent::Tray => layout.tray_pid_file,\n })\n}\n\n/// Returns the exact Windie-owned paths that uninstall may remove.\n///\n/// The data root is the only recursive target. Installed binaries are always\n/// individual files inside the configured install directory; the directory\n/// itself is never removed because it may contain unrelated user programs.\npub fn uninstall_plan() -> Result<UninstallPlan> {\n let user_home = absolute_path(&user_home_dir()?)?;\n let windie_home = absolute_path(&windie_home_dir()?)?;\n let install_dir = absolute_path(&windie_install_dir(&user_home)?)?;\n validate_uninstall_paths(&user_home, &windie_home, &install_dir)?;\n\n Ok(UninstallPlan {\n windie_home,\n install_dir: install_dir.clone(),\n binaries: [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect(),\n })\n}\n\n/// Removes the exact paths in an uninstall plan.\n///\n/// On Windows, a running `windie.exe` cannot remove itself. When the current\n/// executable is one of the planned binaries, this function schedules a\n/// short-lived PowerShell cleanup process and returns before that process\n/// removes the files after Windie exits.\npub fn remove_uninstall_plan(plan: &UninstallPlan) -> Result<UninstallCleanup> {\n validate_uninstall_plan(plan, &absolute_path(&user_home_dir()?)?)?;\n\n let current_executable = absolute_path(&std::env::current_exe()?).ok();\n let deferred_binaries = if cfg!(windows)\n && current_executable\n .as_ref()\n .is_some_and(|path| plan.binaries.iter().any(|binary| binary == path))\n {\n plan.binaries.clone()\n } else {\n Vec::new()\n };\n\n let mut removed_binaries = Vec::new();\n for path in &plan.binaries {\n if deferred_binaries.contains(path) {\n continue;\n }\n if let Some(removed) = remove_owned_file(path)? {\n removed_binaries.push(removed);\n }\n }\n\n let cleanup_scheduled = !deferred_binaries.is_empty();\n if cleanup_scheduled {\n schedule_windows_cleanup(plan)?;\n }\n\n let removed_data = match fs::symlink_metadata(&plan.windie_home) {\n Ok(_) => {\n ensure_owned_directory(&plan.windie_home)?;\n if cleanup_scheduled {\n false\n } else {\n fs::remove_dir_all(&plan.windie_home).with_context(|| {\n format!(\n \"failed to remove Windie data {}\",\n plan.windie_home.display()\n )\n })?;\n true\n }\n }\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,\n Err(error) => {\n return Err(error).with_context(|| {\n format!(\n \"failed to inspect Windie data {}\",\n plan.windie_home.display()\n )\n });\n }\n };\n\n Ok(UninstallCleanup {\n removed_data,\n removed_binaries,\n deferred_binaries,\n cleanup_scheduled,\n })\n}\n\n/// Lists keys currently present in Windie's provider-key environment file.\npub fn list_env_keys() -> Result<Vec<String>> {\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file)\n .with_context(|| format!(\"failed to read {}\", layout.env_file.display()))?;\n let mut keys = text\n .lines()\n .filter_map(env_line_key)\n .map(str::to_string)\n .collect::<Vec<_>>();\n keys.sort();\n keys.dedup();\n\n Ok(keys)\n}\n\n/// Reads one provider-key value from Windie's `~/.windie/.env` file.\npub fn env_value(key: &str) -> Result<Option<String>> {\n let path = env_file_path()?;\n if !path.exists() {\n return Ok(None);\n }\n\n let text =\n fs::read_to_string(&path).with_context(|| format!(\"failed to read {}\", path.display()))?;\n\n Ok(text.lines().find_map(|line| env_line_value(line, key)))\n}\n\n/// Sets one or more provider-key environment values in `~/.windie/.env`.\npub fn set_env_values(assignments: &[(String, String)]) -> Result<PathBuf> {\n if assignments.is_empty() {\n return Err(anyhow!(\"at least one KEY=value assignment is required\"));\n }\n for (key, _) in assignments {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let mut lines = text.lines().map(str::to_string).collect::<Vec<_>>();\n\n for (key, value) in assignments {\n set_env_line(&mut lines, key, value);\n }\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Removes one or more provider-key environment values from `~/.windie/.env`.\npub fn unset_env_values(keys: &[String]) -> Result<PathBuf> {\n if keys.is_empty() {\n return Err(anyhow!(\"at least one environment key is required\"));\n }\n for key in keys {\n validate_env_key(key)?;\n }\n\n let layout = ensure_windie_layout()?;\n let text = fs::read_to_string(&layout.env_file).unwrap_or_default();\n let lines = text\n .lines()\n .filter(|line| {\n let Some(key) = env_line_key(line) else {\n return true;\n };\n !keys.iter().any(|removed| removed == key)\n })\n .map(str::to_string)\n .collect::<Vec<_>>();\n\n write_env_lines(&layout.env_file, &lines)?;\n\n Ok(layout.env_file)\n}\n\n/// Installs or verifies one approved Windie runtime dependency.\npub fn install_target(target: &str) -> Result<InstallReport> {\n ensure_windie_layout()?;\n\n match target {\n \"bifrost\" => Ok(InstallReport {\n target: target.to_string(),\n message: \"Bifrost is provided by the Windie-owned bundled binary\".to_string(),\n status: InstallStatus::Detected,\n }),\n \"cua-driver\" => install_cua_driver(),\n \"desktop-commander\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Desktop Commander\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"blender-mcp\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Blender MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"brightdata\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"Node.js runtime is ready for Bright Data MCP\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n \"basic-memory\" => {\n let status = runtime::ensure_provider_runtime(target)?;\n Ok(InstallReport {\n target: target.to_string(),\n message: \"uv runtime is ready for Basic Memory\".to_string(),\n status: if status {\n InstallStatus::Installed\n } else {\n InstallStatus::Detected\n },\n })\n }\n _ => Err(anyhow!(\"unknown install target: {target}\")),\n }\n}\n\n/// Returns the current user-local Windie layout without creating directories.\nfn windie_layout() -> Result<WindieLayout> {\n let root = windie_home_dir()?;\n\n Ok(WindieLayout {\n env_file: root.join(ENV_FILE_NAME),\n bifrost_dir: root.join(BIFROST_DIR),\n benchmarks_dir: root.join(BENCHMARK_DIR),\n gateway_log_file: root.join(BIFROST_DIR).join(GATEWAY_LOG_FILE_NAME),\n gateway_pid_file: root.join(BIFROST_DIR).join(GATEWAY_PID_FILE_NAME),\n api_log_file: root.join(API_LOG_FILE_NAME),\n api_pid_file: root.join(API_PID_FILE_NAME),\n inspector_log_file: root.join(INSPECTOR_LOG_FILE_NAME),\n inspector_pid_file: root.join(INSPECTOR_PID_FILE_NAME),\n tray_log_file: root.join(TRAY_LOG_FILE_NAME),\n tray_pid_file: root.join(TRAY_PID_FILE_NAME),\n root,\n })\n}\n\n/// Returns the configured user-local directory containing Windie binaries.\nfn windie_install_dir(user_home: &Path) -> Result<PathBuf> {\n Ok(env::var_os(\"WINDIE_INSTALL_DIR\")\n .map(PathBuf::from)\n .unwrap_or_else(|| user_home.join(\".local\").join(\"bin\")))\n}\n\n/// Returns an absolute, lexical path without requiring the target to exist.\nfn absolute_path(path: &Path) -> Result<PathBuf> {\n let path = if path.is_absolute() {\n path.to_path_buf()\n } else {\n env::current_dir()?.join(path)\n };\n let mut normalized = PathBuf::new();\n for component in path.components() {\n match component {\n std::path::Component::CurDir => {}\n std::path::Component::ParentDir => {\n normalized.pop();\n }\n _ => normalized.push(component.as_os_str()),\n }\n }\n Ok(normalized)\n}\n\n/// Rejects paths that could turn uninstall into a broad recursive deletion.\nfn validate_uninstall_paths(\n user_home: &Path,\n windie_home: &Path,\n install_dir: &Path,\n) -> Result<()> {\n let root = Path::new(std::path::MAIN_SEPARATOR_STR);\n if windie_home == root || windie_home == user_home || windie_home.parent().is_none() {\n return Err(anyhow!(\n \"refusing to uninstall: Windie data path is unsafe: {}\",\n windie_home.display()\n ));\n }\n if !matches!(\n windie_home.file_name().and_then(|name| name.to_str()),\n Some(\".windie\") | Some(\"windie\")\n ) {\n return Err(anyhow!(\n \"refusing to uninstall: data path must be a Windie directory: {}\",\n windie_home.display()\n ));\n }\n if install_dir == root\n || install_dir.parent().is_none()\n || install_dir == user_home\n || install_dir == windie_home\n || install_dir.starts_with(windie_home)\n || windie_home.starts_with(install_dir)\n {\n return Err(anyhow!(\n \"refusing to uninstall: install and data paths overlap unsafely\"\n ));\n }\n Ok(())\n}\n\n/// Validates both the safe roots and the exact binary list selected by Windie.\nfn validate_uninstall_plan(plan: &UninstallPlan, user_home: &Path) -> Result<()> {\n validate_uninstall_paths(user_home, &plan.windie_home, &plan.install_dir)?;\n let expected = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| plan.install_dir.join(executable_name(name)))\n .collect::<Vec<_>>();\n if plan.binaries != expected {\n return Err(anyhow!(\n \"refusing to uninstall: plan contains paths outside Windie's owned binaries\"\n ));\n }\n Ok(())\n}\n\n/// Refuses to follow or remove a symlink at an owned file target.\nfn remove_owned_file(path: &Path) -> Result<Option<PathBuf>> {\n let metadata = match fs::symlink_metadata(path) {\n Ok(metadata) => metadata,\n Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n Err(error) => {\n return Err(error).with_context(|| format!(\"failed to inspect {}\", path.display()));\n }\n };\n if metadata.file_type().is_symlink() {\n return Err(anyhow!(\n \"refusing to remove symlink at Windie-owned path {}\",\n path.display()\n ));\n }\n if !metadata.is_file() {\n return Err(anyhow!(\n \"refusing to remove non-file at Windie-owned path {}\",\n path.display()\n ));\n }\n fs::remove_file(path).with_context(|| format!(\"failed to remove {}\", path.display()))?;\n Ok(Some(path.to_path_buf()))\n}\n\n/// Refuses to recursively remove a symlink in place of Windie's data root.\nfn ensure_owned_directory(path: &Path) -> Result<()> {\n let metadata = fs::symlink_metadata(path)\n .with_context(|| format!(\"failed to inspect Windie data {}\", path.display()))?;\n if metadata.file_type().is_symlink() || !metadata.is_dir() {\n return Err(anyhow!(\n \"refusing to recursively remove non-directory Windie data path {}\",\n path.display()\n ));\n }\n Ok(())\n}\n\n/// Returns the platform-specific installed executable filename.\nfn executable_name(name: &str) -> String {\n if cfg!(windows) {\n format!(\"{name}.exe\")\n } else {\n name.to_string()\n }\n}\n\n#[cfg(windows)]\nfn schedule_windows_cleanup(plan: &UninstallPlan) -> Result<()> {\n let mut paths = plan.binaries.clone();\n paths.push(plan.windie_home.clone());\n let script = \"Start-Sleep -Milliseconds 500; foreach ($path in $args) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue } }\";\n let mut command = Command::new(\"powershell.exe\");\n command.args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-WindowStyle\",\n \"Hidden\",\n \"-Command\",\n script,\n \"--\",\n ]);\n command.args(paths);\n command\n .spawn()\n .context(\"failed to schedule Windows Windie cleanup\")?;\n Ok(())\n}\n\n#[cfg(not(windows))]\nfn schedule_windows_cleanup(_plan: &UninstallPlan) -> Result<()> {\n Ok(())\n}\n\n/// Installs CUA Driver using its public upstream installer when needed.\nfn install_cua_driver() -> Result<InstallReport> {\n #[cfg(target_os = \"windows\")]\n {\n if runtime::resolve_command(\"cua-driver\").is_err() {\n install_cua_driver_windows()?;\n }\n runtime::resolve_command(\"cua-driver\")?;\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed or verified cua-driver with its official Windows installer\"\n .to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(target_os = \"macos\")]\n if runtime::resolve_command(\"cua-driver\").is_ok() && runtime::cua_driver_app_available() {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver and its macOS application are already available\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n #[cfg(not(target_os = \"macos\"))]\n if command_exists(\"cua-driver\") {\n return Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"cua-driver is already available on PATH\".to_string(),\n status: InstallStatus::Detected,\n });\n }\n\n require_command(\"curl\")?;\n require_command(\"bash\")?;\n\n let status = Command::new(\"bash\")\n .arg(\"-c\")\n .arg(format!(\"curl -fsSL {CUA_DRIVER_INSTALL_URL} | bash\"))\n .status()\n .context(\"failed to start cua-driver installer\")?;\n if !status.success() {\n return Err(anyhow!(\"cua-driver installer failed\"));\n }\n\n runtime::resolve_command(\"cua-driver\")\n .context(\"cua-driver installer completed but cua-driver is not resolvable\")?;\n\n #[cfg(target_os = \"macos\")]\n if !runtime::cua_driver_app_available() {\n return Err(anyhow!(\n \"cua-driver installer completed but /Applications/CuaDriver.app is not installed\"\n ));\n }\n\n Ok(InstallReport {\n target: \"cua-driver\".to_string(),\n message: \"installed cua-driver with the public trycua installer\".to_string(),\n status: InstallStatus::Installed,\n })\n}\n\n#[cfg(target_os = \"windows\")]\nfn install_cua_driver_windows() -> Result<()> {\n const INSTALL_URL: &str =\n \"https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1\";\n let script = format!(\n \"$ErrorActionPreference = 'Stop'; $path = Join-Path $env:TEMP 'windie-cua-install.ps1'; Invoke-WebRequest -UseBasicParsing -Uri '{INSTALL_URL}' -OutFile $path; & $path; $code = $LASTEXITCODE; Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue; exit $code\"\n );\n let status = Command::new(\"powershell.exe\")\n .args([\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-Command\",\n &script,\n ])\n .status()\n .context(\"failed to start the CUA Driver Windows installer\")?;\n if !status.success() {\n return Err(anyhow!(\n \"CUA Driver Windows installer failed with status {status}\"\n ));\n }\n runtime::resolve_command(\"cua-driver\")\n .context(\"CUA Driver installer completed but cua-driver is not resolvable\")?;\n Ok(())\n}\n\n/// Requires one executable to be available on PATH.\nfn require_command(program: &str) -> Result<()> {\n if command_exists(program) {\n return Ok(());\n }\n\n Err(anyhow!(\n \"required command is not available on PATH: {program}\"\n ))\n}\n\n/// Returns whether one executable is available on PATH.\nfn command_exists(program: &str) -> bool {\n let Some(paths) = env::var_os(\"PATH\") else {\n return false;\n };\n\n env::split_paths(&paths).any(|path| {\n if path.join(program).is_file() {\n return true;\n }\n #[cfg(target_os = \"windows\")]\n {\n return [\".exe\", \".cmd\", \".bat\"]\n .iter()\n .any(|suffix| path.join(format!(\"{program}{suffix}\")).is_file());\n }\n #[cfg(not(target_os = \"windows\"))]\n false\n })\n}\n\n/// Validates a `.env` key that Windie is allowed to write.\nfn validate_env_key(key: &str) -> Result<()> {\n if key.is_empty() {\n return Err(anyhow!(\"environment key cannot be empty\"));\n }\n if key\n .bytes()\n .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_')\n {\n if LLM_ENV_KEYS.contains(&key) {\n return Err(anyhow!(\n \"LLM provider keys are managed by Bifrost; use `windie onboard`: {key}\"\n ));\n }\n return Ok(());\n }\n\n Err(anyhow!(\n \"environment key must contain only uppercase letters, digits, and underscores: {key}\"\n ))\n}\n\n/// Returns the key assigned by one `.env` line, if the line assigns a value.\nfn env_line_key(line: &str) -> Option<&str> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, _) = line.split_once('=')?;\n let key = key.trim();\n if key.is_empty() { None } else { Some(key) }\n}\n\n/// Returns the value assigned to a target key by one `.env` line.\nfn env_line_value(line: &str, target_key: &str) -> Option<String> {\n let line = line.trim();\n if line.is_empty() || line.starts_with('#') {\n return None;\n }\n let line = line.strip_prefix(\"export \").unwrap_or(line).trim();\n let (key, value) = line.split_once('=')?;\n if key.trim() != target_key {\n return None;\n }\n\n Some(unquote_env_value(value.trim()).to_string())\n}\n\n/// Removes matching quote characters around a full `.env` value.\nfn unquote_env_value(value: &str) -> &str {\n if value.len() >= 2 {\n let bytes = value.as_bytes();\n if (bytes[0] == b'\"' && bytes[value.len() - 1] == b'\"')\n || (bytes[0] == b'\\'' && bytes[value.len() - 1] == b'\\'')\n {\n return &value[1..value.len() - 1];\n }\n }\n\n value\n}\n\n/// Inserts or replaces one key assignment in an in-memory env file.\nfn set_env_line(lines: &mut Vec<String>, key: &str, value: &str) {\n let replacement = format!(\"{key}={value}\");\n for line in lines.iter_mut() {\n if env_line_key(line).is_some_and(|line_key| line_key == key) {\n *line = replacement;\n return;\n }\n }\n\n lines.push(replacement);\n}\n\n/// Writes env file lines with a stable trailing newline.\nfn write_env_lines(path: &Path, lines: &[String]) -> Result<()> {\n let text = if lines.is_empty() {\n String::new()\n } else {\n format!(\"{}\\n\", lines.join(\"\\n\"))\n };\n fs::write(path, text).with_context(|| format!(\"failed to write {}\", path.display()))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn uninstall_rejects_home_root_and_overlapping_paths() {\n let home = Path::new(\"/Users/example\");\n let install = home.join(\".local/bin\");\n\n assert!(validate_uninstall_paths(home, home, &install).is_err());\n assert!(validate_uninstall_paths(home, Path::new(\"/\"), &install).is_err());\n assert!(validate_uninstall_paths(home, &home.join(\".windie\"), home).is_err());\n assert!(\n validate_uninstall_paths(home, &home.join(\".windie\"), &home.join(\".windie/bin\"))\n .is_err()\n );\n }\n\n #[test]\n fn uninstall_removes_only_exact_windie_paths() {\n let root =\n std::env::temp_dir().join(format!(\"windie-uninstall-test-{}\", std::process::id()));\n let home = root.join(\"home\");\n let windie_home = home.join(\".windie\");\n let install_dir = home.join(\".local/bin\");\n let windie_binary = install_dir.join(executable_name(\"windie\"));\n let unrelated_file = install_dir.join(\"unrelated\");\n fs::create_dir_all(&windie_home).unwrap();\n fs::create_dir_all(&install_dir).unwrap();\n fs::write(&windie_binary, \"owned\").unwrap();\n fs::write(&unrelated_file, \"preserve\").unwrap();\n\n let binaries = [\"windie\", \"bifrost\", \"windie-inspector\"]\n .into_iter()\n .map(|name| install_dir.join(executable_name(name)))\n .collect();\n let plan = UninstallPlan {\n windie_home: windie_home.clone(),\n install_dir: install_dir.clone(),\n binaries,\n };\n let cleanup = remove_uninstall_plan(&plan).unwrap();\n\n assert!(cleanup.removed_data);\n assert!(!windie_home.exists());\n assert!(!windie_binary.exists());\n assert!(unrelated_file.exists());\n assert!(install_dir.exists());\n fs::remove_dir_all(root).unwrap();\n }\n\n #[test]\n fn env_line_key_reads_plain_and_export_assignments() {\n assert_eq!(env_line_key(\"OPENAI_API_KEY=value\"), Some(\"OPENAI_API_KEY\"));\n assert_eq!(\n env_line_key(\"export OPENROUTER_API_KEY=value\"),\n Some(\"OPENROUTER_API_KEY\")\n );\n assert_eq!(env_line_key(\"# OPENAI_API_KEY=value\"), None);\n }\n\n #[test]\n fn env_line_value_reads_plain_export_and_quoted_assignments() {\n assert_eq!(\n env_line_value(\"OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n Some(\"value\".to_string())\n );\n assert_eq!(\n env_line_value(\"export OPENROUTER_API_KEY='quoted'\", \"OPENROUTER_API_KEY\"),\n Some(\"quoted\".to_string())\n );\n assert_eq!(\n env_line_value(\"BRIGHTDATA_API_TOKEN=\\\"bright\\\"\", \"BRIGHTDATA_API_TOKEN\"),\n Some(\"bright\".to_string())\n );\n assert_eq!(\n env_line_value(\"# OPENAI_API_KEY=value\", \"OPENAI_API_KEY\"),\n None\n );\n }\n\n #[test]\n fn set_env_line_replaces_existing_key() {\n let mut lines = vec![\n \"OPENAI_API_KEY=old\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string(),\n ];\n\n set_env_line(&mut lines, \"OPENAI_API_KEY\", \"new\");\n\n assert_eq!(\n lines,\n vec![\n \"OPENAI_API_KEY=new\".to_string(),\n \"OPENROUTER_API_KEY=keep\".to_string()\n ]\n );\n }\n\n #[test]\n fn rejects_lowercase_env_key() {\n let error = validate_env_key(\"openai_api_key\").unwrap_err();\n\n assert!(error.to_string().contains(\"uppercase\"));\n }\n\n #[test]\n fn rejects_llm_provider_env_key() {\n let error = validate_env_key(\"OPENAI_API_KEY\").unwrap_err();\n\n assert!(error.to_string().contains(\"managed by Bifrost\"));\n }\n}\n"}}}}