diff --git a/apps/backend/core/auth.py b/apps/backend/core/auth.py index 01dab6ad..ce105a0c 100644 --- a/apps/backend/core/auth.py +++ b/apps/backend/core/auth.py @@ -36,6 +36,8 @@ SDK_ENV_VARS = [ "DISABLE_TELEMETRY", "DISABLE_COST_WARNINGS", "API_TIMEOUT_MS", + # Windows-specific: Git Bash path for Claude Code CLI + "CLAUDE_CODE_GIT_BASH_PATH", ] @@ -215,6 +217,85 @@ def require_auth_token() -> str: return token +def _find_git_bash_path() -> str | None: + """ + Find git-bash (bash.exe) path on Windows. + + Uses 'where git' to find git.exe, then derives bash.exe location from it. + Git for Windows installs bash.exe in the 'bin' directory alongside git.exe + or in the parent 'bin' directory when git.exe is in 'cmd'. + + Returns: + Full path to bash.exe if found, None otherwise + """ + if platform.system() != "Windows": + return None + + # If already set in environment, use that + existing = os.environ.get("CLAUDE_CODE_GIT_BASH_PATH") + if existing and os.path.exists(existing): + return existing + + git_path = None + + # Method 1: Use 'where' command to find git.exe + try: + # Use where.exe explicitly for reliability + result = subprocess.run( + ["where.exe", "git"], + capture_output=True, + text=True, + timeout=5, + shell=False, + ) + + if result.returncode == 0 and result.stdout.strip(): + git_paths = result.stdout.strip().splitlines() + if git_paths: + git_path = git_paths[0].strip() + except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError): + # Intentionally suppress errors - best-effort detection with fallback to common paths + pass + + # Method 2: Check common installation paths if 'where' didn't work + if not git_path: + common_git_paths = [ + os.path.expandvars(r"%PROGRAMFILES%\Git\cmd\git.exe"), + os.path.expandvars(r"%PROGRAMFILES%\Git\bin\git.exe"), + os.path.expandvars(r"%PROGRAMFILES(X86)%\Git\cmd\git.exe"), + os.path.expandvars(r"%LOCALAPPDATA%\Programs\Git\cmd\git.exe"), + ] + for path in common_git_paths: + if os.path.exists(path): + git_path = path + break + + if not git_path: + return None + + # Derive bash.exe location from git.exe location + # Git for Windows structure: + # C:\...\Git\cmd\git.exe -> bash.exe is at C:\...\Git\bin\bash.exe + # C:\...\Git\bin\git.exe -> bash.exe is at C:\...\Git\bin\bash.exe + # C:\...\Git\mingw64\bin\git.exe -> bash.exe is at C:\...\Git\bin\bash.exe + git_dir = os.path.dirname(git_path) + git_parent = os.path.dirname(git_dir) + git_grandparent = os.path.dirname(git_parent) + + # Check common bash.exe locations relative to git installation + possible_bash_paths = [ + os.path.join(git_parent, "bin", "bash.exe"), # cmd -> bin + os.path.join(git_dir, "bash.exe"), # If git.exe is in bin + os.path.join(git_grandparent, "bin", "bash.exe"), # mingw64/bin -> bin + ] + + for bash_path in possible_bash_paths: + if os.path.exists(bash_path): + return bash_path + + return None + + def get_sdk_env_vars() -> dict[str, str]: """ Get environment variables to pass to SDK. @@ -222,6 +303,8 @@ def get_sdk_env_vars() -> dict[str, str]: Collects relevant env vars (ANTHROPIC_BASE_URL, etc.) that should be passed through to the claude-agent-sdk subprocess. + On Windows, auto-detects CLAUDE_CODE_GIT_BASH_PATH if not already set. + Returns: Dict of env var name -> value for non-empty vars """ @@ -230,6 +313,14 @@ def get_sdk_env_vars() -> dict[str, str]: value = os.environ.get(var) if value: env[var] = value + + # On Windows, auto-detect git-bash path if not already set + # Claude Code CLI requires bash.exe to run on Windows + if platform.system() == "Windows" and "CLAUDE_CODE_GIT_BASH_PATH" not in env: + bash_path = _find_git_bash_path() + if bash_path: + env["CLAUDE_CODE_GIT_BASH_PATH"] = bash_path + return env diff --git a/apps/backend/core/client.py b/apps/backend/core/client.py index 3d8dbe8d..d0f05033 100644 --- a/apps/backend/core/client.py +++ b/apps/backend/core/client.py @@ -16,6 +16,7 @@ import copy import json import logging import os +import platform import threading import time from pathlib import Path @@ -488,6 +489,12 @@ def create_client( # Collect env vars to pass to SDK (ANTHROPIC_BASE_URL, etc.) sdk_env = get_sdk_env_vars() + # Debug: Log git-bash path detection on Windows + if "CLAUDE_CODE_GIT_BASH_PATH" in sdk_env: + logger.info(f"Git Bash path found: {sdk_env['CLAUDE_CODE_GIT_BASH_PATH']}") + elif platform.system() == "Windows": + logger.warning("Git Bash path not detected on Windows!") + # Check if Linear integration is enabled linear_enabled = is_linear_enabled() linear_api_key = os.environ.get("LINEAR_API_KEY", "") diff --git a/apps/frontend/src/main/agent/agent-process.ts b/apps/frontend/src/main/agent/agent-process.ts index 60bc8fe3..03010bf9 100644 --- a/apps/frontend/src/main/agent/agent-process.ts +++ b/apps/frontend/src/main/agent/agent-process.ts @@ -17,6 +17,62 @@ import { readSettingsFile } from '../settings-utils'; import type { AppSettings } from '../../shared/types/settings'; import { getOAuthModeClearVars } from './env-utils'; import { getAugmentedEnv } from '../env-utils'; +import { getToolInfo } from '../cli-tool-manager'; + + +function deriveGitBashPath(gitExePath: string): string | null { + if (process.platform !== 'win32') { + return null; + } + + try { + const gitDir = path.dirname(gitExePath); // e.g., D:\...\Git\mingw64\bin + const gitDirName = path.basename(gitDir).toLowerCase(); + + // Find Git installation root + let gitRoot: string; + + if (gitDirName === 'cmd') { + // .../Git/cmd/git.exe -> .../Git + gitRoot = path.dirname(gitDir); + } else if (gitDirName === 'bin') { + // Could be .../Git/bin/git.exe OR .../Git/mingw64/bin/git.exe + const parent = path.dirname(gitDir); + const parentName = path.basename(parent).toLowerCase(); + if (parentName === 'mingw64' || parentName === 'mingw32') { + // .../Git/mingw64/bin/git.exe -> .../Git + gitRoot = path.dirname(parent); + } else { + // .../Git/bin/git.exe -> .../Git + gitRoot = parent; + } + } else { + // Unknown structure - try to find 'bin' sibling + gitRoot = path.dirname(gitDir); + } + + // Bash.exe is in Git/bin/bash.exe + const bashPath = path.join(gitRoot, 'bin', 'bash.exe'); + + if (existsSync(bashPath)) { + console.log('[AgentProcess] Derived git-bash path:', bashPath); + return bashPath; + } + + // Fallback: check one level up if gitRoot didn't work + const altBashPath = path.join(path.dirname(gitRoot), 'bin', 'bash.exe'); + if (existsSync(altBashPath)) { + console.log('[AgentProcess] Found git-bash at alternate path:', altBashPath); + return altBashPath; + } + + console.warn('[AgentProcess] Could not find bash.exe from git path:', gitExePath); + return null; + } catch (error) { + console.error('[AgentProcess] Error deriving git-bash path:', error); + return null; + } +} /** * Process spawning and lifecycle management @@ -59,8 +115,28 @@ export class AgentProcessManager { // Use getAugmentedEnv() to ensure common tool paths (dotnet, homebrew, etc.) // are available even when app is launched from Finder/Dock const augmentedEnv = getAugmentedEnv(); + + // On Windows, detect and pass git-bash path for Claude Code CLI + // Electron can detect git via where.exe, but Python subprocess may not have the same PATH + const gitBashEnv: Record = {}; + if (process.platform === 'win32' && !process.env.CLAUDE_CODE_GIT_BASH_PATH) { + try { + const gitInfo = getToolInfo('git'); + if (gitInfo.found && gitInfo.path) { + const bashPath = deriveGitBashPath(gitInfo.path); + if (bashPath) { + gitBashEnv['CLAUDE_CODE_GIT_BASH_PATH'] = bashPath; + console.log('[AgentProcess] Setting CLAUDE_CODE_GIT_BASH_PATH:', bashPath); + } + } + } catch (error) { + console.warn('[AgentProcess] Failed to detect git-bash path:', error); + } + } + return { ...augmentedEnv, + ...gitBashEnv, ...extraEnv, ...profileEnv, PYTHONUNBUFFERED: '1', diff --git a/apps/frontend/src/main/cli-tool-manager.ts b/apps/frontend/src/main/cli-tool-manager.ts index d2f715b8..4334d27e 100644 --- a/apps/frontend/src/main/cli-tool-manager.ts +++ b/apps/frontend/src/main/cli-tool-manager.ts @@ -28,6 +28,11 @@ import { app } from 'electron'; import { findExecutable } from './env-utils'; import type { ToolDetectionResult } from '../shared/types'; import { findHomebrewPython as findHomebrewPythonUtil } from './utils/homebrew-python'; +import { + getWindowsExecutablePaths, + WINDOWS_GIT_PATHS, + findWindowsExecutableViaWhere, +} from './utils/windows-paths'; /** * Supported CLI tools managed by this system @@ -392,7 +397,40 @@ class CLIToolManager { } } - // 4. Not found - fallback to 'git' + // 4. Windows-specific detection using 'where' command (most reliable for custom installs) + if (process.platform === 'win32') { + // First try 'where' command - finds git regardless of installation location + const whereGitPath = findWindowsExecutableViaWhere('git', '[Git]'); + if (whereGitPath) { + const validation = this.validateGit(whereGitPath); + if (validation.valid) { + return { + found: true, + path: whereGitPath, + version: validation.version, + source: 'system-path', + message: `Using Windows Git: ${whereGitPath}`, + }; + } + } + + // Fallback to checking common installation paths + const windowsPaths = getWindowsExecutablePaths(WINDOWS_GIT_PATHS, '[Git]'); + for (const winGitPath of windowsPaths) { + const validation = this.validateGit(winGitPath); + if (validation.valid) { + return { + found: true, + path: winGitPath, + version: validation.version, + source: 'system-path', + message: `Using Windows Git: ${winGitPath}`, + }; + } + } + } + + // 5. Not found - fallback to 'git' return { found: false, source: 'fallback', diff --git a/apps/frontend/src/main/utils/windows-paths.ts b/apps/frontend/src/main/utils/windows-paths.ts new file mode 100644 index 00000000..27ce4f9b --- /dev/null +++ b/apps/frontend/src/main/utils/windows-paths.ts @@ -0,0 +1,172 @@ +/** + * Windows Executable Path Discovery Utility + * + * Provides reusable logic for finding Windows executables in common installation + * locations. Handles environment variable expansion and security validation. + * + * Used by cli-tool-manager.ts for Git, GitHub CLI, Claude CLI, etc. + * Follows the same pattern as homebrew-python.ts for platform-specific detection. + */ + +import { existsSync } from 'fs'; +import { execFileSync } from 'child_process'; +import path from 'path'; +import os from 'os'; + +export interface WindowsToolPaths { + toolName: string; + executable: string; + patterns: string[]; +} + +export const WINDOWS_GIT_PATHS: WindowsToolPaths = { + toolName: 'Git', + executable: 'git.exe', + patterns: [ + '%PROGRAMFILES%\\Git\\cmd', + '%PROGRAMFILES(X86)%\\Git\\cmd', + '%LOCALAPPDATA%\\Programs\\Git\\cmd', + '%USERPROFILE%\\scoop\\apps\\git\\current\\cmd', + '%PROGRAMFILES%\\Git\\bin', + '%PROGRAMFILES(X86)%\\Git\\bin', + '%PROGRAMFILES%\\Git\\mingw64\\bin', + ], +}; + +function isSecurePath(pathStr: string): boolean { + const dangerousPatterns = [ + /[;&|`$(){}[\]<>!]/, // Shell metacharacters + /\.\.\//, // Unix directory traversal + /\.\.\\/, // Windows directory traversal + /[\r\n]/, // Newlines (command injection) + ]; + + for (const pattern of dangerousPatterns) { + if (pattern.test(pathStr)) { + return false; + } + } + + return true; +} + +export function expandWindowsPath(pathPattern: string): string | null { + const envVars: Record = { + '%PROGRAMFILES%': process.env.ProgramFiles || 'C:\\Program Files', + '%PROGRAMFILES(X86)%': process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', + '%LOCALAPPDATA%': process.env.LOCALAPPDATA, + '%APPDATA%': process.env.APPDATA, + '%USERPROFILE%': process.env.USERPROFILE || os.homedir(), + }; + + let expandedPath = pathPattern; + + for (const [placeholder, value] of Object.entries(envVars)) { + if (expandedPath.includes(placeholder)) { + if (!value) { + return null; + } + expandedPath = expandedPath.replace(placeholder, value); + } + } + + // Verify no unexpanded placeholders remain (indicates unknown variable) + if (/%[^%]+%/.test(expandedPath)) { + return null; + } + + // Normalize the path (resolve double backslashes, etc.) + return path.normalize(expandedPath); +} + +export function getWindowsExecutablePaths( + toolPaths: WindowsToolPaths, + logPrefix: string = '[Windows Paths]' +): string[] { + // Only run on Windows + if (process.platform !== 'win32') { + return []; + } + + const validPaths: string[] = []; + + for (const pattern of toolPaths.patterns) { + const expandedDir = expandWindowsPath(pattern); + + if (!expandedDir) { + console.warn(`${logPrefix} Could not expand path pattern: ${pattern}`); + continue; + } + + const fullPath = path.join(expandedDir, toolPaths.executable); + + // Security validation - reject potentially dangerous paths + if (!isSecurePath(fullPath)) { + console.warn(`${logPrefix} Path failed security validation: ${fullPath}`); + continue; + } + + if (existsSync(fullPath)) { + validPaths.push(fullPath); + } + } + + return validPaths; +} + +/** + * Find a Windows executable using the `where` command. + * This is the most reliable method as it searches: + * - All directories in PATH + * - App Paths registry entries + * - Current directory + * + * Works regardless of where the tool is installed (custom paths, different drives, etc.) + * + * @param executable - The executable name (e.g., 'git', 'gh', 'python') + * @param logPrefix - Prefix for console logging + * @returns The full path to the executable, or null if not found + */ +export function findWindowsExecutableViaWhere( + executable: string, + logPrefix: string = '[Windows Where]' +): string | null { + if (process.platform !== 'win32') { + return null; + } + + // Security: Only allow simple executable names (alphanumeric, dash, underscore, dot) + if (!/^[\w.-]+$/.test(executable)) { + console.warn(`${logPrefix} Invalid executable name: ${executable}`); + return null; + } + + try { + // Use 'where' command to find the executable + // where.exe is a built-in Windows command that finds executables + const result = execFileSync('where.exe', [executable], { + encoding: 'utf-8', + timeout: 5000, + windowsHide: true, + }).trim(); + + // 'where' returns multiple paths separated by newlines if found in multiple locations + // We take the first one (highest priority in PATH) + const paths = result.split(/\r?\n/).filter(p => p.trim()); + + if (paths.length > 0) { + const foundPath = paths[0].trim(); + + // Validate the path exists and is secure + if (existsSync(foundPath) && isSecurePath(foundPath)) { + console.log(`${logPrefix} Found via where: ${foundPath}`); + return foundPath; + } + } + + return null; + } catch { + // 'where' returns exit code 1 if not found, which throws an error + return null; + } +} diff --git a/package-lock.json b/package-lock.json index 7d729b30..d16fa47c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -268,6 +268,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -652,6 +653,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -695,6 +697,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -734,6 +737,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1599,7 +1603,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -1621,7 +1624,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1638,7 +1640,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1653,7 +1654,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2807,6 +2807,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2828,6 +2829,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -2840,6 +2842,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2855,6 +2858,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -3242,6 +3246,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3258,6 +3263,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -3275,6 +3281,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -5512,8 +5519,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5744,6 +5750,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5754,6 +5761,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5861,6 +5869,7 @@ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", @@ -6275,6 +6284,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6344,6 +6354,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6964,6 +6975,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7777,8 +7789,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -8164,6 +8175,7 @@ "integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.0.12", "builder-util": "26.0.11", @@ -8259,8 +8271,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dotenv": { "version": "16.6.1", @@ -8335,6 +8346,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", @@ -8583,7 +8595,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -8604,7 +8615,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -8979,6 +8989,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10204,6 +10215,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -11006,6 +11018,7 @@ "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -11742,7 +11755,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -13812,6 +13824,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13887,7 +13900,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -13905,7 +13917,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -13926,7 +13937,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -13942,7 +13952,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -14070,6 +14079,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14079,6 +14089,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14118,8 +14129,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-markdown": { "version": "10.1.0", @@ -14554,7 +14564,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -15445,7 +15454,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -15555,7 +15565,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -15619,7 +15628,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15711,6 +15719,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15973,6 +15982,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16322,6 +16332,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16914,6 +16925,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17454,6 +17466,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.2.tgz", "integrity": "sha512-b8L8yn4rIVfiXyHAmnr52/ZEpDumlT0bmxiq3Ws1ybrinhflGpt12Hvv54kYnEsGPRs6o/Ka3/ppA2OWY21IVg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }