diff --git a/- b/- new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 7ea135d..0a88417 100755 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,21 @@ config.local.json # Temporary files *.tmp +<<<<<<< HEAD +*.temp + +# Web development +node_modules/ +.next/ +out/ +.env*.local +.npm/ + +# Build artifacts +dist/ +build/ +*.egg-info/ + +======= *.temp +>>>>>>> main diff --git a/.idx/dev.nix b/.idx/dev.nix new file mode 100644 index 0000000..6073082 --- /dev/null +++ b/.idx/dev.nix @@ -0,0 +1,58 @@ +# To learn more about how to use Nix to configure your environment +# see: https://firebase.google.com/docs/studio/customize-workspace +{ pkgs, ... }: { + # Which nixpkgs channel to use. + channel = "stable-24.05"; # or "unstable" + + # Use https://search.nixos.org/packages to find packages + packages = [ + pkgs.go + pkgs.python311 + pkgs.python311Packages.pip + pkgs.nmap + pkgs.dnsutils + pkgs.ollama + # pkgs.nodejs_20 + # pkgs.nodePackages.nodemon + ]; + + # Sets environment variables in the workspace + env = {}; + idx = { + # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" + extensions = [ + # "vscodevim.vim" + ]; + + # Enable previews + previews = { + enable = true; + previews = { + # web = { + # # Example: run "npm run dev" with PORT set to IDX's defined port for previews, + # # and show it in IDX's web preview panel + # command = ["npm" "run" "dev"]; + # manager = "web"; + # env = { + # # Environment variables to set for your server + # PORT = "$PORT"; + # }; + # }; + }; + }; + + # Workspace lifecycle hooks + workspace = { + # Runs when a workspace is first created + onCreate = { + # Example: install JS dependencies from NPM + # npm-install = "npm install"; + }; + # Runs when the workspace is (re)started + onStart = { + # Example: start a background task to watch and re-build backend code + # watch-backend = "npm run watch-backend"; + }; + }; + }; +} diff --git a/neurorift_main.py b/neurorift_main.py index e169413..92901a9 100755 --- a/neurorift_main.py +++ b/neurorift_main.py @@ -1,5 +1,15 @@ #!/usr/bin/env python3 """ +<<<<<<< HEAD +VulnForge - Educational Cybersecurity Research Framework +For authorized testing and educational purposes only. +""" + +# ╔══════════════════════════════════════════════════════════╗ +# ║ VulnForge - Built with Blood by DemonKing369.0 👑 ║ +# ║ GitHub: https://github.com/Arunking9 ║ +# ║ AI-Powered Security Framework for Bug Bounty Warriors ⚔️║ +======= NeuroRift - Terminal-Based Multi-Agent Intelligence System For authorized security testing and educational purposes only. @@ -10,6 +20,7 @@ # ║ NeuroRift - Designed by demonking369 🧠 ║ # ║ GitHub: https://github.com/demonking369/NeuroRift ║ # ║ Multi-Agent Intelligence for Security Research ⚡ ║ +>>>>>>> main # ╚══════════════════════════════════════════════════════════╝ import os @@ -22,6 +33,16 @@ from pathlib import Path import logging import asyncio +<<<<<<< HEAD +from rich.console import Console +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, TextColumn +from typing import Optional + +from recon_module import EnhancedReconModule +from ai_integration import AIAnalyzer, OllamaClient +from ai_orchestrator import AIOrchestrator # New Import +======= from dotenv import load_dotenv from rich.console import Console from rich.panel import Panel @@ -56,6 +77,7 @@ from modules.ai.agents import NRPlanner, NROperator, NRAnalyst, NRScribe from modules.tools.base import ToolMode from modules.config.config_wizard import ConfigWizard +>>>>>>> main class NeuroRift: @@ -64,6 +86,15 @@ def __init__(self): self.base_dir = Path.home() / ".neurorift" self.results_dir = self.base_dir / "results" self.tools_dir = self.base_dir / "tools" +<<<<<<< HEAD + self.setup_directories() + self.setup_logging() + self.console = Console() + + # Initialize AI components + self.ollama = OllamaClient() + self.ai_analyzer = AIAnalyzer(self.ollama) +======= self.setup_logging() self.console = Console() @@ -86,6 +117,7 @@ def __init__(self): self.operator = NROperator(self.execution_manager) self.analyst = NRAnalyst(self.ollama) self.scribe = NRScribe(self.ollama) +>>>>>>> main def setup_directories(self): """Create necessary directories""" @@ -107,6 +139,14 @@ def banner(self): """Display tool banner""" banner_text = f""" ╔══════════════════════════════════════════════════════════════╗ +<<<<<<< HEAD +║ NeuroRift v{self.version} ║ +║ Advanced Cognitive Security Framework ║ +║ For Authorized Testing Only ║ +╚══════════════════════════════════════════════════════════════╝ + """ + self.console.print(Panel(banner_text, style="bold cyan")) +======= ║ NeuroRift v{self.version:10} ║ ║ Terminal-Based Multi-Agent Intelligence System ║ ║ ║ @@ -117,6 +157,7 @@ def banner(self): Thanks to the open-source projects that inspired and supported NeuroRift. """ self.console.print(Panel(banner_text, style="bold blue")) +>>>>>>> main def check_tools(self): """Check if required tools are installed""" @@ -210,6 +251,13 @@ def ask_ai(self, question: str): "[bold red]Error: Could not get response from AI[/bold red]" ) +<<<<<<< HEAD + def generate_tool(self, description: str): + """Generate a custom tool using AI and save it to custom_tools directory.""" + try: + # Use os.path.expanduser to properly handle home directory + tool_dir = os.path.expanduser("~/.vulnforge/custom_tools") +======= @RateLimiter(max_calls=5, time_window=60) def generate_tool(self, description: str, identifier: str = 'default'): """Generate a custom tool using AI and save it to custom_tools directory. @@ -231,13 +279,28 @@ def generate_tool(self, description: str, identifier: str = 'default'): # Use os.path.expanduser to properly handle home directory tool_dir = Path.home() / ".neurorift" / "custom_tools" +>>>>>>> main self.console.print( f"[bold blue]Resolved custom_tools directory:[/bold blue] {tool_dir}" ) +<<<<<<< HEAD + # Create directory with proper permissions + try: + os.makedirs(tool_dir, mode=0o755, exist_ok=True) + self.console.print( + f"[bold green]✓ Directory created/verified:[/bold green] {tool_dir}" + ) + except PermissionError as e: + self.logger.error("Permission error creating directory: %s", e) + return + except Exception as e: + self.logger.error("Error creating directory: %s", e) +======= # SECURITY: Create directory with secure permissions (0o700) if not FilePermissionManager.create_secure_directory(tool_dir, mode=0o700): self.logger.error("Failed to create secure directory") +>>>>>>> main return metadata_path = os.path.join(tool_dir, "metadata.json") @@ -256,12 +319,20 @@ def generate_tool(self, description: str, identifier: str = 'default'): ) return +<<<<<<< HEAD + # Extract a reasonable filename from the description +======= # SECURITY: Extract and sanitize filename +>>>>>>> main import re base_name = re.sub(r"[^a-zA-Z0-9]+", "_", description.strip().lower())[ :32 ].strip("_") +<<<<<<< HEAD + filename = f"{base_name or 'custom_tool'}_{int(time.time())}.py" + tool_path = os.path.join(tool_dir, filename) +======= filename = sanitize_filename(f"{base_name or 'custom_tool'}_{int(time.time())}.py") # SECURITY: Validate path to prevent traversal @@ -269,13 +340,20 @@ def generate_tool(self, description: str, identifier: str = 'default'): if not tool_path: self.logger.error("Invalid tool path") return +>>>>>>> main # Write the generated tool to file with open(tool_path, "w", encoding="utf-8") as f: f.write(response) +<<<<<<< HEAD + # SECURITY FIX: Set secure file permissions (0o600) for generated tool files + # This ensures only the owner can read/write the file + os.chmod(tool_path, 0o600) +======= # SECURITY: Set secure file permissions (0o600) for generated tool files FilePermissionManager.set_secure_permissions(tool_path, mode=0o600) +>>>>>>> main self.console.print( f"[bold green]✓ Tool generated and saved to:[/bold green] {tool_path}" ) @@ -314,6 +392,16 @@ def generate_tool(self, description: str, identifier: str = 'default'): self.logger.error("Unexpected error in generate_tool: %s", e) return False +<<<<<<< HEAD + + def list_custom_tools(self): + """List all custom tools in the custom_tools directory.""" + try: + tool_dir = os.path.expanduser("~/.vulnforge/custom_tools") + metadata_path = os.path.join(tool_dir, "metadata.json") + + if not os.path.exists(tool_dir): +======= def list_custom_tools(self): """List all custom tools in the custom_tools directory.""" try: @@ -329,12 +417,17 @@ def list_custom_tools(self): metadata_path = tool_dir / "metadata.json" if not tool_dir.exists(): +>>>>>>> main self.console.print( "[bold yellow]No custom tools directory found.[/bold yellow]" ) return +<<<<<<< HEAD + if not os.path.exists(metadata_path): +======= if not metadata_path.exists(): +>>>>>>> main self.console.print( "[bold yellow]No custom tools metadata found.[/bold yellow]" ) @@ -370,6 +463,48 @@ def list_custom_tools(self): self.logger.error("Unexpected error in list_custom_tools: %s", e) return [] +<<<<<<< HEAD + def launch_web_mode(self, mode="real"): + """Launch the Web Mode UI""" + # First check relative to script (for dev mode/source run) + web_ui_dir = Path(__file__).parent / "web-ui" + + # If not found, check current working directory (for installed package run) + if not web_ui_dir.exists(): + web_ui_dir = Path.cwd() / "web-ui" + + if not web_ui_dir.exists(): + self.console.print(f"[bold red]Error: web-ui directory not found at {web_ui_dir} or relative to script.[/bold red]") + return + + # Check for npm + if not self.is_tool_installed("npm"): + self.console.print("[bold red]Error: npm is not installed. Please install Node.js and npm.[/bold red]") + return + + self.console.print(f"[bold green]Launching Web Mode UI ({mode.upper()} MODE)...[/bold green]") + self.console.print(f"[blue]Working directory: {web_ui_dir}[/blue]") + + # Set environment variable for the mode + env = os.environ.copy() + env['NEXT_PUBLIC_NR_MODE'] = mode + + try: + # check if node_modules exists, if not install dependencies + if not (web_ui_dir / "node_modules").exists(): + self.console.print("[yellow]Installing dependencies...[/yellow]") + subprocess.run(["npm", "install"], cwd=web_ui_dir, check=True) + + # Run npm run dev + subprocess.run(["npm", "run", "dev"], cwd=web_ui_dir, env=env, check=True) + except subprocess.CalledProcessError as e: + self.console.print(f"[bold red]Error launching Web UI: {e}[/bold red]") + except KeyboardInterrupt: + self.console.print("\n[bold yellow]Web UI stopped.[/bold yellow]") + + +======= +>>>>>>> main async def dev_mode_shell(vf, session_dir): console = Console() @@ -377,6 +512,35 @@ async def dev_mode_shell(vf, session_dir): console.print( "\n[bold magenta]Entering Dev Mode Shell. Type 'help' for commands.[/bold magenta]" ) +<<<<<<< HEAD + # ... (content of dev_mode_shell remains same, just ensuring launch_web_mode is NOT here) + + + + +async def _async_main(): + parser = argparse.ArgumentParser( + description=""" +╔══════════════════════════════════════════════════════════╗ +║ NeuroRift - Advanced Cognitive Security Framework ║ +║ GitHub: https://github.com/Arunking9 ║ +║ AI-Powered Security Framework for Bug Bounty Warriors ⚔️ ║ +╚══════════════════════════════════════════════════════════╝ + +A powerful AI-driven security testing framework for authorized penetration testing and bug bounty hunting. + +Key Features: +• AI-Autonomous Operation (--ai-only) +• Advanced Reconnaissance +• Stealth Mode Capabilities +• Multi-format Reporting +• Custom Tool Generation +• Interactive AI Assistant +• Development Mode +• Web Mode UI (--web-mode) + +For detailed documentation, visit: https://github.com/Arunking9/NeuroRift +======= while True: try: cmd = input("[dev-mode]> ").strip() @@ -462,17 +626,28 @@ def get_parser(): Thanks to the open-source projects that inspired and supported NeuroRift. For detailed documentation, visit: https://github.com/demonking369/NeuroRift +>>>>>>> main """, formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument("--target", "-t", help="Target domain or IP") parser.add_argument( +<<<<<<< HEAD + "--mode", + "-m", + choices=["recon", "scan", "web", "exploit"], + default="recon", + help="Operation mode", + ) + # ... (rest of arguments remain unchanged) ... +======= "--operation-mode", "-m", choices=["recon", "scan", "web", "exploit"], default="recon", help="Operation mode (legacy: recon, scan, web, exploit)", ) +>>>>>>> main parser.add_argument("--output", "-o", help="Output file path") parser.add_argument( "--output-format", @@ -493,6 +668,15 @@ def get_parser(): "--dev-mode", action="store_true", help="Enable development mode" ) parser.add_argument( +<<<<<<< HEAD + "--web-mode", "--webmod", action="store_true", help="Launch the Web Mode UI" + ) + parser.add_argument( + "--prototype", action="store_true", help="Launch Web Mode in Prototype (Mock) Mode" + ) + parser.add_argument( +======= +>>>>>>> main "--verbose", "-v", action="store_true", help="Show detailed logs" ) parser.add_argument( @@ -502,6 +686,9 @@ def get_parser(): "--ai-pipeline", action="store_true", help="Enable the advanced multi-prompt AI pipeline." ) parser.add_argument( +<<<<<<< HEAD + "--prompt-dir", help="Directory for the AI pipeline prompts.", default="AI_Propmt/system-prompts-and-models-of-ai-tools" +======= "--prompt-dir", help="Directory for the AI pipeline prompts.", default="prompts/system_prompts" ) parser.add_argument( @@ -538,6 +725,7 @@ def get_parser(): ) parser.add_argument( "--configure", action="store_true", help="🆕 Launch interactive configuration wizard" +>>>>>>> main ) # Add subparsers for commands @@ -559,6 +747,18 @@ def get_parser(): list_tools_parser = subparsers.add_parser('list-tools', help='List all custom tools') list_tools_parser.add_argument('--verbose', action='store_true', help='Show detailed tool information') +<<<<<<< HEAD + args = parser.parse_args() + + # Initialize NeuroRift + nr = NeuroRift() + nr.banner() + + # Handle Web Mode + if args.web_mode: + mode = "prototype" if args.prototype else "real" + nr.launch_web_mode(mode=mode) +======= # Dark web OSINT command (Robin integration) darkweb_parser = subparsers.add_parser( 'darkweb', help='Run the Robin dark web OSINT workflow' @@ -727,6 +927,7 @@ async def _async_main(args): print(f"Error running uninstall script: {e}") except KeyboardInterrupt: print("\nUninstall cancelled by user") +>>>>>>> main return # Handle AI Pipeline Mode @@ -744,6 +945,11 @@ async def _async_main(args): orchestrator.execute_task(f"Perform a security scan on {args.target}") return +<<<<<<< HEAD + # Set logging level based on verbose flag + if args.verbose: + nr.logger.setLevel(logging.DEBUG) +======= # Handle Agentic AI Mode if args.agentic: vf.agentic_mode = True @@ -764,12 +970,22 @@ async def _async_main(args): # Set logging level based on verbose flag if args.verbose: vf.logger.setLevel(logging.DEBUG) +>>>>>>> main # Handle commands if args.command == "ask-ai": if args.dangerous and not args.confirm_danger: print("Error: --dangerous mode requires --confirm-danger flag") return +<<<<<<< HEAD + nr.ask_ai(args.question) + return + elif args.command == "generate-tool": + nr.generate_tool(args.description) + return + elif args.command == "list-tools": + nr.list_custom_tools() +======= vf.ask_ai(args.question) return elif args.command == "generate-tool": @@ -791,21 +1007,34 @@ async def _async_main(args): threads=args.threads, output=args.output, ) +>>>>>>> main return # Check tools if args.check: +<<<<<<< HEAD + if nr.check_tools(): +======= if vf.check_tools(): +>>>>>>> main print("✓ All required tools are installed") else: print("✗ Some tools are missing") if input("Install missing tools? (y/N): ").lower() == "y": +<<<<<<< HEAD + nr.install_missing_tools() +======= vf.install_missing_tools() +>>>>>>> main return # Install tools if args.install: +<<<<<<< HEAD + nr.install_missing_tools() +======= vf.install_missing_tools() +>>>>>>> main return # Require target for operations @@ -815,6 +1044,10 @@ async def _async_main(args): "Use --target to specify a domain or IP address you own or have authorization to test" ) return +<<<<<<< HEAD + + nr.logger.info("Authorization assumed. Continuing...") +======= # SECURITY: Validate target input if not validate_target(args.target): @@ -831,11 +1064,18 @@ async def _async_main(args): # print("Exiting. Only use this tool on authorized targets.") # return vf.logger.info("Authorization assumed. Continuing...") +>>>>>>> main # Optionally, log a warning to file only logging.getLogger("neurorift").warning( "No explicit authorization prompt. User is responsible for legal/ethical use." ) +<<<<<<< HEAD + # Create session directory + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + session_dir = nr.base_dir / "sessions" / args.target / timestamp + session_dir.mkdir(parents=True, exist_ok=True) +======= # SECURITY: Create session directory with secure permissions timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") @@ -847,13 +1087,18 @@ async def _async_main(args): if not FilePermissionManager.create_secure_directory(session_dir, mode=0o700): print("Error: Failed to create secure session directory") return +>>>>>>> main # Initialize AI controller if needed if args.ai_only or args.ai_debug: from ai_controller import AIController ai_controller = AIController( +<<<<<<< HEAD + str(session_dir), str(nr.base_dir / "configs" / "scan_config.json") +======= str(session_dir), str(vf.base_dir / "configs" / "scan_config.json") +>>>>>>> main ) if not ai_controller.setup_ai(): print("Error: Failed to setup AI system") @@ -863,7 +1108,11 @@ async def _async_main(args): ai_controller.output_format = args.output_format # Execute based on mode +<<<<<<< HEAD + if args.mode == "recon": +======= if args.operation_mode == "recon": +>>>>>>> main print(f"\n🔍 Starting reconnaissance on {args.target}") if args.ai_only: print("Running in AI-only mode - AI will make all decisions") @@ -872,7 +1121,11 @@ async def _async_main(args): if args.stealth: print("Stealth mode enabled - using random delays and rotating user agents") +<<<<<<< HEAD + results = await nr.run_recon(args.target, args.output) +======= results = await vf.run_recon(args.target, args.output) +>>>>>>> main # Display summary console = Console() @@ -893,6 +1146,19 @@ async def _async_main(args): # Start dev mode shell if requested if args.dev_mode: +<<<<<<< HEAD + await dev_mode_shell(nr, session_dir) + + elif args.mode == "scan": + print("Port scanning mode not implemented yet") + + elif args.mode == "web": + # Launch web mode if explicitly selected via mode argument as well + nr.launch_web_mode() + + elif args.mode == "exploit": + print("Exploit mode not implemented yet") +======= await dev_mode_shell(vf, session_dir) elif args.operation_mode == "scan": @@ -1033,10 +1299,14 @@ async def _async_main(args): # Start dev mode shell if requested if args.dev_mode: await dev_mode_shell(vf, session_dir) +>>>>>>> main def main(): """Synchronous entrypoint for console_scripts.""" +<<<<<<< HEAD + asyncio.run(_async_main()) +======= parser, args = get_parser() # Handle web mode BEFORE starting asyncio loop @@ -1085,6 +1355,7 @@ def main(): # Run the main async pipeline asyncio.run(_async_main(args)) +>>>>>>> main if __name__ == "__main__": main() diff --git a/requirements.txt b/requirements.txt index 5c5c2d0..0ad6740 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,12 @@ ollama>=0.1.0 pyyaml>=6.0 requests>=2.26.0 rich>=10.0.0 +<<<<<<< HEAD +aiofiles>=23.1.0 +======= click>=8.0.0 yaspin>=3.0.0 +>>>>>>> main # Security dependencies defusedxml>=0.7.1 # SECURITY: Safe XML parsing to prevent XXE attacks diff --git a/setup.py b/setup.py index bb0bf16..921b90e 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,11 @@ version="1.0.0", packages=find_packages(include=['modules', 'modules.*', 'utils', 'utils.*', 'ai_wrapper', 'ai_wrapper.*']), py_modules=[ +<<<<<<< HEAD + # Top-level modules used by the CLI that are not in a package directory +======= # Top-level modules used by the CLI +>>>>>>> main "neurorift_main", "recon_module", "ai_integration", @@ -49,6 +53,14 @@ "neurorift=neurorift_main:main", ], }, +<<<<<<< HEAD + author="DemonKing369.0", + author_email="arunking9@gmail.com", + description="Advanced Cognitive Security Framework", + long_description=open("README.md").read(), + long_description_content_type="text/markdown", + url="https://github.com/Arunking9/NeuroRift", +======= author="demonking369", author_email="", description="Terminal-Based Multi-Agent Intelligence System for Security Research", @@ -60,6 +72,7 @@ "Documentation": "https://github.com/demonking369/NeuroRift", "Source Code": "https://github.com/demonking369/NeuroRift", }, +>>>>>>> main classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Information Technology", diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..16c7772 --- /dev/null +++ b/shell.nix @@ -0,0 +1,9 @@ + +let + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz"; + pkgs = import nixpkgs { system = "x86_64-linux"; }; + dev = import ./.idx/dev.nix { inherit pkgs; }; +in + pkgs.mkShell { + buildInputs = dev.packages; + } diff --git a/web-ui/next-env.d.ts b/web-ui/next-env.d.ts new file mode 100644 index 0000000..c4b7818 --- /dev/null +++ b/web-ui/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/dev/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/web-ui/next.config.js b/web-ui/next.config.js index dff2ede..6abc89e 100644 --- a/web-ui/next.config.js +++ b/web-ui/next.config.js @@ -1,7 +1,12 @@ /** @type {import('next').NextConfig} */ +<<<<<<< HEAD +const nextConfig = {}; +module.exports = nextConfig; +======= const nextConfig = { output: 'standalone', reactStrictMode: true, } module.exports = nextConfig +>>>>>>> main diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 39a9188..06c3bdb 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -1,4 +1,1942 @@ { +<<<<<<< HEAD + "name": "neurorift-web-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "neurorift-web-ui", + "version": "1.0.0", + "dependencies": { + "clsx": "^2.1.0", + "lucide-react": "^0.309.0", + "next": "^16.1.6", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "recharts": "^2.10.0", + "tailwind-merge": "^2.2.0" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.33", + "tailwindcss": "^3.4.1", + "typescript": "^5.3.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==" + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001767", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", + "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.309.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.309.0.tgz", + "integrity": "sha512-zNVPczuwFrCfksZH3zbd1UDE6/WYhYAdbe2k7CImVyPAkXLgIwbs6eXQ4loigqDnUFjyFYCI5jZ1y10Kqal0dg==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "dependencies": { + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +======= "name": "neurorift-web-ui", "version": "1.0.0", "lockfileVersion": 3, @@ -2318,4 +4256,5 @@ } } } +>>>>>>> main } diff --git a/web-ui/postcss.config.js b/web-ui/postcss.config.js index fef1b22..86b59a0 100644 --- a/web-ui/postcss.config.js +++ b/web-ui/postcss.config.js @@ -3,4 +3,8 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, +<<<<<<< HEAD +}; +======= } +>>>>>>> main diff --git a/web-ui/src/app/api/fs/content/route.ts b/web-ui/src/app/api/fs/content/route.ts new file mode 100644 index 0000000..62a3503 --- /dev/null +++ b/web-ui/src/app/api/fs/content/route.ts @@ -0,0 +1,27 @@ + +import { NextResponse } from 'next/server'; +import fs from 'fs/promises'; +import path from 'path'; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const filePath = searchParams.get('path'); + + if (!filePath) { + return NextResponse.json({ error: 'Path required' }, { status: 400 }); + } + + // Security: Prevent traversal + // This is a local tool, but good practice. + // Ideally, restrict to ~/.vulnforge + // For now, allow reading files we know are artifacts. + + try { + const content = await fs.readFile(filePath, 'utf-8'); + return new NextResponse(content, { + headers: { 'Content-Type': 'text/plain' } + }); + } catch (e) { + return NextResponse.json({ error: 'Failed to read file' }, { status: 500 }); + } +} diff --git a/web-ui/src/app/api/sessions/[id]/artifacts/route.ts b/web-ui/src/app/api/sessions/[id]/artifacts/route.ts new file mode 100644 index 0000000..570a9fd --- /dev/null +++ b/web-ui/src/app/api/sessions/[id]/artifacts/route.ts @@ -0,0 +1,64 @@ + +import { NextResponse } from 'next/server'; +import fs from 'fs/promises'; +import path from 'path'; +import os from 'os'; +import { FileNode } from '@/lib/webmode/adapter/interface'; + +// Recursive function to build file tree +async function buildTree(currentPath: string): Promise { + const stats = await fs.stat(currentPath); + const name = path.basename(currentPath); + + if (stats.isDirectory()) { + const children = await fs.readdir(currentPath); + const childNodes = await Promise.all( + children.map(child => buildTree(path.join(currentPath, child))) + ); + return { + name, + type: 'directory', + path: currentPath, + children: childNodes + }; + } else { + return { + name, + type: 'file', + path: currentPath, + size: stats.size + }; + } +} + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + + // Composite ID: Target__Timestamp + const parts = id.split('__'); + + if (parts.length < 2) { + return NextResponse.json([]); + } + + const target = parts[0]; + const timestamp = parts[1]; + + const sessionDir = path.join(os.homedir(), '.vulnforge', 'sessions', target, timestamp); + + try { + // checks if sessions exist + await fs.access(sessionDir); + + const root = await buildTree(sessionDir); + // Return children of the session dir, not the session dir itself as root + return NextResponse.json(root.children || []); + + } catch (e) { + console.error("Artifact error", e); + return NextResponse.json([], { status: 500 }); + } +} diff --git a/web-ui/src/app/api/sessions/route.ts b/web-ui/src/app/api/sessions/route.ts new file mode 100644 index 0000000..165669c --- /dev/null +++ b/web-ui/src/app/api/sessions/route.ts @@ -0,0 +1,74 @@ + +import { NextResponse } from 'next/server'; +import fs from 'fs/promises'; +import path from 'path'; +import os from 'os'; + +export async function GET() { + try { + const sessionsDir = path.join(os.homedir(), '.vulnforge', 'sessions'); + + // Check if directory exists + try { + await fs.access(sessionsDir); + } catch { + return NextResponse.json([]); + } + + const targets = await fs.readdir(sessionsDir, { withFileTypes: true }); + + const sessions = []; + + for (const target of targets) { + if (!target.isDirectory()) continue; + + const targetPath = path.join(sessionsDir, target.name); + const timestamps = await fs.readdir(targetPath, { withFileTypes: true }); + + for (const ts of timestamps) { + if (!ts.isDirectory()) continue; + + // Try to read metadata if it exists + const sessionPath = path.join(targetPath, ts.name); + let metadata = { toolCount: 0, status: 'completed' }; // Defaults + + // You might want to define a metadata.json in each session folder in the python backend + + sessions.push({ + id: `${target.name}__${ts.name}`, // Composite ID + target: target.name, + startTime: parseTimestamp(ts.name), // Helper needed + mode: 'recon', // Default or read from metadata + status: metadata.status, + toolCount: metadata.toolCount + }); + } + } + + // Sort by recent first + sessions.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()); + + return NextResponse.json(sessions); + } catch (e) { + return NextResponse.json({ error: 'Failed to list sessions' }, { status: 500 }); + } +} + +function parseTimestamp(ts: string) { + // Expected format: YYYYMMDD_HHMMSS + // Simple fallback + try { + const parts = ts.split('_'); + const dateStr = parts[0]; + const timeStr = parts[1]; + const year = parseInt(dateStr.substring(0, 4)); + const month = parseInt(dateStr.substring(4, 6)) - 1; + const day = parseInt(dateStr.substring(6, 8)); + const hour = parseInt(timeStr.substring(0, 2)); + const min = parseInt(timeStr.substring(2, 4)); + const sec = parseInt(timeStr.substring(4, 6)); + return new Date(year, month, day, hour, min, sec).toISOString(); + } catch { + return new Date().toISOString(); + } +} diff --git a/web-ui/src/app/api/tools/run/route.ts b/web-ui/src/app/api/tools/run/route.ts new file mode 100644 index 0000000..f1be9a2 --- /dev/null +++ b/web-ui/src/app/api/tools/run/route.ts @@ -0,0 +1,75 @@ + +import { NextResponse } from 'next/server'; +import { spawn } from 'child_process'; +import path from 'path'; + +const globalExecutions = (global as any)._nr_executions || new Map(); +(global as any)._nr_executions = globalExecutions; + +export async function POST(request: Request) { + try { + const { tool, args } = await request.json(); + + // Security Guard: Only allow specific tools + const allowedTools = ['nmap', 'nuclei', 'subfinder', 'ffuf', 'whois', 'dig']; + if (!allowedTools.includes(tool)) { + return NextResponse.json({ error: 'Tool not allowed' }, { status: 403 }); + } + + const executionId = `exec_${Date.now()}`; + const cmdArgs = args.split(' ').filter((a: string) => a.length > 0); + + console.log(`[API] Spawning: ${tool} ${args}`); + + try { + const child = spawn(tool, cmdArgs, { shell: false }); + + globalExecutions.set(executionId, { + id: executionId, + tool, + args, + status: 'running', + startTime: Date.now(), + stdout: [], + stderr: [], + process: child + }); + + child.stdout.on('data', (data) => { + const lines = data.toString().split('\n'); + const exec = globalExecutions.get(executionId); + if (exec) exec.stdout.push(...lines); + }); + + child.stderr.on('data', (data) => { + const lines = data.toString().split('\n'); + const exec = globalExecutions.get(executionId); + if (exec) exec.stderr.push(...lines); + }); + + child.on('close', (code) => { + const exec = globalExecutions.get(executionId); + if (exec) { + exec.status = code === 0 ? 'completed' : 'failed'; + exec.exitCode = code; + } + }); + + child.on('error', (err) => { + const exec = globalExecutions.get(executionId); + if (exec) { + exec.status = 'failed'; + exec.stderr.push(`Process error: ${err.message}`); + } + }); + + } catch (spawnError) { + return NextResponse.json({ error: `Failed to spawn: ${spawnError}` }, { status: 500 }); + } + + return NextResponse.json({ executionId }); + + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/web-ui/src/app/api/tools/status/[id]/route.ts b/web-ui/src/app/api/tools/status/[id]/route.ts new file mode 100644 index 0000000..0932a41 --- /dev/null +++ b/web-ui/src/app/api/tools/status/[id]/route.ts @@ -0,0 +1,29 @@ + +import { NextResponse } from 'next/server'; + +const globalExecutions = (global as any)._nr_executions || new Map(); +(global as any)._nr_executions = globalExecutions; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + const exec = globalExecutions.get(id); + + if (!exec) { + return NextResponse.json({ error: 'Execution not found' }, { status: 404 }); + } + + // Return status and latest logs + // (In a real app, optimize log slicing) + return NextResponse.json({ + id: exec.id, + tool: exec.tool, + status: exec.status, + startTime: exec.startTime, + duration: exec.status === 'completed' ? (Date.now() - exec.startTime) : undefined, + stdout: exec.stdout, + stderr: exec.stderr + }); +} diff --git a/web-ui/src/app/globals.css b/web-ui/src/app/globals.css new file mode 100644 index 0000000..a4555fe --- /dev/null +++ b/web-ui/src/app/globals.css @@ -0,0 +1,23 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --neuro-bg: 10, 12, 18; + --neuro-surface: 18, 22, 32; +} + +* { + scrollbar-width: thin; + scrollbar-color: rgba(45, 55, 72, 0.6) transparent; +} + +body { + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background-color: rgba(var(--neuro-bg), 1); + color: rgba(226, 232, 240, 1); +} + +.command-chip { + @apply flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-neuro-surface/80 border border-neuro-border/60 text-xs text-neuro-text-secondary hover:text-neuro-text-primary hover:border-cyan-400/40 transition cursor-pointer; +} diff --git a/web-ui/src/app/layout.tsx b/web-ui/src/app/layout.tsx index 44b4478..bd23ec5 100644 --- a/web-ui/src/app/layout.tsx +++ b/web-ui/src/app/layout.tsx @@ -1,4 +1,18 @@ import type { Metadata } from 'next'; +<<<<<<< HEAD +import './globals.css'; + +export const metadata: Metadata = { + title: 'NeuroRift — Web Mode', + description: 'AI-powered cybersecurity control plane', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} +======= import { Inter } from 'next/font/google'; import '@/styles/globals.css'; import { CommandCenterFrame } from '@/components/webmode/CommandCenterFrame'; @@ -23,6 +37,7 @@ export default function RootLayout({ {children} +>>>>>>> main ); diff --git a/web-ui/src/app/page.tsx b/web-ui/src/app/page.tsx index dff3d35..86b52bf 100644 --- a/web-ui/src/app/page.tsx +++ b/web-ui/src/app/page.tsx @@ -1,7 +1,152 @@ 'use client'; +<<<<<<< HEAD +import { useState } from 'react'; +import { WebModeProvider } from '@/components/webmode/WebModeProvider'; +import { CommandPanel } from '@/components/webmode/panels/CommandPanel'; +import { AgentGraphPanel } from '@/components/webmode/panels/AgentGraphPanel'; +import { MemoryPulsePanel } from '@/components/webmode/panels/MemoryPulsePanel'; +import { PermissionsPanel } from '@/components/webmode/panels/PermissionsPanel'; +import { ConfigMatrixPanel } from '@/components/webmode/panels/ConfigMatrixPanel'; +import { OnlineModePanel } from '@/components/webmode/panels/OnlineModePanel'; +import { ActivityStreamPanel } from '@/components/webmode/panels/ActivityStreamPanel'; +import { ExecutionTimelinePanel } from '@/components/webmode/panels/ExecutionTimelinePanel'; +import { IntentFlowPanel } from '@/components/webmode/panels/IntentFlowPanel'; +import { ManualToolPanel } from '@/components/webmode/panels/ManualToolPanel'; +import { SessionManagerPanel } from '@/components/webmode/panels/SessionManagerPanel'; +import { ArtifactViewerPanel } from '@/components/webmode/panels/ArtifactViewerPanel'; +import { cn } from '@/lib/utils'; +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; // Import context hook + +const TABS = [ + { id: 'command', label: 'Command' }, + { id: 'operator', label: 'Operator' }, // New Operator Tab + { id: 'agents', label: 'Agents' }, + { id: 'memory', label: 'Memory' }, + { id: 'permissions', label: 'Permissions' }, + { id: 'config', label: 'Config' }, + { id: 'online', label: 'Online' }, +] as const; + +type TabId = (typeof TABS)[number]['id']; + +function ModeBanner() { + const { adapter } = useWebModeContext(); + // Check if adapter is prototype (this check is a bit naive, ideally we check config or adapter type property) + // But since we don't expose type, we can infer or rely on config injection + const isPrototype = adapter.constructor.name === 'PrototypeAdapter'; + + if (isPrototype) { + return ( +
+ ⚠ Prototype Mode — Simulated Environment +
+ ); + } + return null; +} + + +function Dashboard() { + const [activeTab, setActiveTab] = useState('command'); + + return ( +
+ + {/* Header */} +
+
+
+
N
+
+

NeuroRift

+
Web Mode — Control Plane
+
+
+
+
+ Online +
+
+
+ + {/* Tab Navigation */} + + + {/* Main Content */} +
+ {activeTab === 'operator' ? ( +
+
+
+ +
+
+ +
+
+
+ +
+
+ ) : ( +
+ {/* Primary Panel */} +
+ {activeTab === 'command' && } + {activeTab === 'agents' && } + {activeTab === 'memory' && } + {activeTab === 'permissions' && } + {activeTab === 'config' && } + {activeTab === 'online' && } +
+ + {/* Sidebar */} +
+
+ +
+
+ +
+
+ +
+
+
+ )} +
+
+ ); +} + +export default function WebModePage() { + return ( + + + + ); +======= import { CommandCenter } from '@/components/webmode/CommandCenter'; export default function DashboardPage() { return ; +>>>>>>> main } diff --git a/web-ui/src/components/webmode/WebModeProvider.tsx b/web-ui/src/components/webmode/WebModeProvider.tsx index 5252d06..22f7d72 100644 --- a/web-ui/src/components/webmode/WebModeProvider.tsx +++ b/web-ui/src/components/webmode/WebModeProvider.tsx @@ -1,5 +1,162 @@ 'use client'; +<<<<<<< HEAD +import React, { createContext, useContext, useReducer, useEffect, ReactNode, useMemo, useState } from 'react'; +import { + WebModeState, + WebModeAction, + DeviceTier, + IntentDraft, + PermissionRequest, + WebModeConfig, + AgentNode, + MemoryMetric +} from '../../lib/webmode/types'; +import { + webModeReducer, + initialState, + bootSequence, + useDeviceTier +} from '../../lib/webmode/state'; +import { initialConfig } from '../../lib/webmode/config-schema'; +import { WebModeAdapter } from '../../lib/webmode/adapter/interface'; +import { PrototypeAdapter } from '../../lib/webmode/adapter/prototype'; +import { RealAdapter } from '../../lib/webmode/adapter/real'; + +// 1. Context Definition +interface WebModeContextType { + state: WebModeState; + config: WebModeConfig; + adapter: WebModeAdapter; // Expose adapter + dispatch: React.Dispatch; + updateConfig: (section: keyof WebModeConfig, key: string, value: any) => void; + queueIntent: (text: string) => void; + resolvePermission: (id: string, decision: 'approved' | 'denied') => void; + sendSignal: (signal: string) => void; + + // Legacy/Computed properties for compatibility + deviceTier: DeviceTier; + controlMode: 'read' | 'control'; + phase: string; + lastSignal: string; + intentQueue: IntentDraft[]; + agentState: Record; + memoryPulse: MemoryMetric[]; + permissionQueue: PermissionRequest[]; +} + +const WebModeContext = createContext(undefined); + +// 2. Provider Component +export function WebModeProvider({ children }: { children: ReactNode }) { + const [state, dispatch] = useReducer(webModeReducer, initialState); + const [config, setConfig] = useState(initialConfig); + const deviceTier = useDeviceTier(); + + // Initialize Adapter based on Env Var + const adapter = useMemo(() => { + // In a real app we might use a compile-time constant or a robust env check. + // Here we assume NEXT_PUBLIC_NR_MODE is injected by the launcher. + const mode = process.env.NEXT_PUBLIC_NR_MODE; + console.log(`[WebMode] Initializing Adapter. Mode: ${mode}`); + if (mode === 'real') { + return new RealAdapter(); + } + return new PrototypeAdapter(); + }, []); + + // Boot Sequence + useEffect(() => { + let mounted = true; + (async () => { + await bootSequence(dispatch); + if (mounted) { + // Hydrate initial data from adapter + try { + const sessions = await adapter.listSessions(); + console.log('[WebMode] Loaded sessions:', sessions.length); + // dispatch({ type: 'LOAD_SESSIONS', payload: sessions }); // TODO: Add action + } catch (e) { + console.error('[WebMode] Failed to load initial data:', e); + } + } + })(); + return () => { mounted = false; }; + }, [adapter]); + + // Helper Actions + const updateConfig = (section: keyof WebModeConfig, key: string, value: any) => { + setConfig(prev => ({ + ...prev, + [section]: { ...prev[section], [key]: value } + })); + }; + + const queueIntent = (text: string) => { + const id = `intent-${Date.now()}`; + dispatch({ + type: 'QUEUE_INTENT', + payload: { + id, + text, + status: 'analyzing', + confidence: 0, + timestamp: Date.now(), // timestamp added to match local type, might need to check IntentDraft definition + origin: 'user' + } as IntentDraft + }); + + // Simulation of async analysis (would be replaced by backend/adapter call in real usage) + setTimeout(() => { + dispatch({ + type: 'UPDATE_INTENT_STATUS', + payload: { id, status: 'policy-check', outcome: 'Likely compliant.' } + }); + }, 800); + + setTimeout(() => { + dispatch({ + type: 'UPDATE_INTENT_STATUS', + payload: { id, status: 'ready', outcome: 'Approved.' } + }); + }, 1600); + }; + + const resolvePermission = (id: string, approved: boolean | 'approved' | 'denied') => { + // Handle legacy boolean vs new string union + const decision = approved === true || approved === 'approved'; + dispatch({ type: 'RESOLVE_PERMISSION', payload: { id, approved: decision } }); + }; + + const sendSignal = (signal: string) => { + dispatch({ type: 'SIGNAL', payload: signal }); + }; + + // Computed properties + const controlMode = (config.onlineMode.access === 'control' ? 'control' : 'read') as 'read' | 'control'; + + return ( + resolvePermission(id, decision), + sendSignal, + + // Compatibility + deviceTier, + controlMode, + phase: state.phase, + lastSignal: state.lastSignal, + intentQueue: state.intentQueue, + agentState: state.agentState, + memoryPulse: state.memoryPulse, + permissionQueue: state.permissionQueue + }}> +======= import { createContext, useContext } from 'react'; import { useDeviceTier, useWebModeState } from '@/lib/webmode/state'; import type { DeviceTier, WebModeConfig } from '@/lib/webmode/types'; @@ -36,11 +193,19 @@ export function WebModeProvider({ children }: { children: React.ReactNode }) { sendSignal, }} > +>>>>>>> main {children} ); } +<<<<<<< HEAD +export const useWebModeContext = () => { + const context = useContext(WebModeContext); + if (!context) throw new Error('useWebModeContext must be used within WebModeProvider'); + return context; +}; +======= export function useWebModeContext() { const context = useContext(WebModeContext); if (!context) { @@ -48,3 +213,4 @@ export function useWebModeContext() { } return context; } +>>>>>>> main diff --git a/web-ui/src/components/webmode/panels/ActivityStreamPanel.tsx b/web-ui/src/components/webmode/panels/ActivityStreamPanel.tsx index 0306940..a11e24f 100644 --- a/web-ui/src/components/webmode/panels/ActivityStreamPanel.tsx +++ b/web-ui/src/components/webmode/panels/ActivityStreamPanel.tsx @@ -1,5 +1,32 @@ 'use client'; +<<<<<<< HEAD +import { Activity } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const EVENTS = [ + { id: 1, text: 'Agent "planner" transitioned to idle', time: '2s ago', type: 'info' }, + { id: 2, text: 'Policy lattice compiled (0 conflicts)', time: '5s ago', type: 'success' }, + { id: 3, text: 'Memory decay cycle completed', time: '8s ago', type: 'info' }, +]; + +export function ActivityStreamPanel() { + return ( +
+
+ + Activity Stream +
+
+ {EVENTS.map(ev => ( +
+
+ {ev.text} + {ev.time} +
+ ))} +
+======= import { useSignalStream } from '@/lib/webmode/stream'; import { cn } from '@/lib/utils'; @@ -25,6 +52,7 @@ export function ActivityStreamPanel() {

{event.label}

))} +>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/AgentGraphPanel.tsx b/web-ui/src/components/webmode/panels/AgentGraphPanel.tsx index 7382c74..24283f5 100644 --- a/web-ui/src/components/webmode/panels/AgentGraphPanel.tsx +++ b/web-ui/src/components/webmode/panels/AgentGraphPanel.tsx @@ -1,5 +1,110 @@ 'use client'; +<<<<<<< HEAD +import { useState } from 'react'; +import { Cpu, GitBranch, Eye } from 'lucide-react'; +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +import { cn } from '@/lib/utils'; + +const NODE_POSITIONS: Record = { + planner: { x: 20, y: 20 }, + operator: { x: 70, y: 15 }, + analyst: { x: 75, y: 70 }, + scribe: { x: 25, y: 75 }, +}; + +const AGENT_ICONS: Record> = { + planner: Cpu, + operator: GitBranch, + analyst: Eye, + scribe: Cpu, +}; + +const STATUS_COLORS: Record = { + idle: 'border-neutral-600 bg-neutral-800/60', + planning: 'border-cyan-400/60 bg-cyan-900/30', + executing: 'border-emerald-400/60 bg-emerald-900/30', + analyzing: 'border-amber-400/60 bg-amber-900/30', + error: 'border-rose-400/60 bg-rose-900/30', +}; + +const PULSE_COLORS: Record = { + idle: 'bg-neutral-500', + planning: 'bg-cyan-400', + executing: 'bg-emerald-400', + analyzing: 'bg-amber-400', + error: 'bg-rose-500', +}; + +export function AgentGraphPanel() { + const { agentState } = useWebModeContext(); + const [hoveredAgent, setHoveredAgent] = useState(null); + + const agents = Object.values(agentState); + + return ( +
+
+ + Agent Dependency Graph +
+ +
+ {/* SVG Connections */} + + {agents.map(agent => + agent.dependencies?.map((depId: string) => { + const from = NODE_POSITIONS[depId]; + const to = NODE_POSITIONS[agent.id]; + if (!from || !to) return null; + return ( + + ); + }) + )} + + + {/* Agent Nodes */} + {agents.map(agent => { + const pos = NODE_POSITIONS[agent.id]; + if (!pos) return null; + const IconComponent = AGENT_ICONS[agent.id] || Cpu; + return ( +
setHoveredAgent(agent.id)} + onMouseLeave={() => setHoveredAgent(null)} + > +
+ +
+
+
{agent.name}
+ + {/* Introspection Card */} +
+
{agent.role}
+
{agent.name}
+
+
State{agent.status}
+
Load{Math.round(agent.load * 100)}%
+
Task{agent.currentTask || 'Standby'}
+
+
+
+ ); + })} +
+ + {/* Legend */} +
+ {Object.entries(PULSE_COLORS).map(([status, color]) => ( + {status} + ))} +
+======= import { useNeuroRift } from '@/lib/hooks'; import { cn } from '@/lib/utils'; @@ -44,6 +149,7 @@ export function AgentGraphPanel() {
); })} +>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/ArtifactViewerPanel.tsx b/web-ui/src/components/webmode/panels/ArtifactViewerPanel.tsx new file mode 100644 index 0000000..dbb9255 --- /dev/null +++ b/web-ui/src/components/webmode/panels/ArtifactViewerPanel.tsx @@ -0,0 +1,122 @@ + +import React, { useState, useEffect } from 'react'; +import { useWebModeContext } from '../WebModeProvider'; +import { FileText, Folder, Eye, FileCode, ChevronRight, ChevronDown } from 'lucide-react'; +import { FileNode } from '@/lib/webmode/adapter/interface'; + +interface TreeNodeProps { + node: FileNode; + depth?: number; + onSelect: (path: string) => void; + selectedPath?: string; +} + +const TreeNode: React.FC = ({ node, depth = 0, onSelect, selectedPath }) => { + const [expanded, setExpanded] = useState(false); + const isDir = node.type === 'directory'; + const isSelected = node.path === selectedPath; + + const handleClick = () => { + if (isDir) { + setExpanded(!expanded); + } else { + onSelect(node.path); + } + }; + + return ( +
+
+ {isDir ? ( + {expanded ? : } + ) : } + + {isDir ? : } + {node.name} +
+ {isDir && expanded && node.children && ( +
+ {node.children.map(child => ( + + ))} +
+ )} +
+ ); +}; + +export function ArtifactViewerPanel() { + const { adapter } = useWebModeContext(); + const [artifacts, setArtifacts] = useState([]); + const [selectedPath, setSelectedPath] = useState(); + const [content, setContent] = useState(''); + const [loading, setLoading] = useState(false); + const [sessionId, setSessionId] = useState('current'); // Simplified for now, should link to selected session + + useEffect(() => { + const loadNodes = async () => { + try { + const data = await adapter.listArtifacts(sessionId); + setArtifacts(data); + } catch (e) { + console.error("Failed", e); + } + }; + loadNodes(); + }, [sessionId, adapter]); + + const handleSelect = async (path: string) => { + setSelectedPath(path); + setLoading(true); + try { + const data = await adapter.getArtifactContent(path); + setContent(data); + } catch (e) { + setContent(`Error loading content: ${e}`); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* File Tree */} +
+
+ + ARTIFACTS +
+
+ {artifacts.map(node => ( + + ))} +
+
+ + {/* Content Preview */} +
+
+
+ + {selectedPath || 'Select file...'} +
+
+
+ {loading ? ( +
Loading content...
+ ) : selectedPath ? ( +
{content}
+ ) : ( +
No file selected
+ )} +
+
+
+ ); +} diff --git a/web-ui/src/components/webmode/panels/CommandPanel.tsx b/web-ui/src/components/webmode/panels/CommandPanel.tsx index 8c17445..fd01911 100644 --- a/web-ui/src/components/webmode/panels/CommandPanel.tsx +++ b/web-ui/src/components/webmode/panels/CommandPanel.tsx @@ -1,10 +1,18 @@ 'use client'; +<<<<<<< HEAD +import { useState, useEffect } from 'react'; +import { Bot, Send, Shield, StepForward } from 'lucide-react'; +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +import { getWebSocket } from '@/lib/webmode/stream'; +import { cn } from '@/lib/utils'; +======= import { useState } from 'react'; import { Bot, Send, PauseCircle, PlayCircle, StepForward, Shield } from 'lucide-react'; import { getWebSocket } from '@/lib/websocket'; import { cn } from '@/lib/utils'; import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +>>>>>>> main interface CommandMessage { id: string; @@ -16,6 +24,33 @@ interface CommandMessage { const COMMAND_PRESETS = ['continue', 'pause', 'resume', 'map intent flow', 'audit policies']; export function CommandPanel() { +<<<<<<< HEAD + const { controlMode, sendSignal, queueIntent, intentQueue = [] } = useWebModeContext(); + const [input, setInput] = useState(''); + const [messages, setMessages] = useState([ + { id: 'seed-1', role: 'assistant', text: 'Command fabric online. Provide intent or directive to orchestrate OpenClaw + NeuroRift.', timestamp: '' } + ]); + + useEffect(() => { + setMessages(prev => prev.map(msg => msg.id === 'seed-1' && !msg.timestamp ? { ...msg, timestamp: new Date().toLocaleTimeString() } : msg)); + }, []); + + const submitMessage = (message: string) => { + if (!message.trim()) return; + const entry: CommandMessage = { id: `${Date.now()}-user`, role: 'user', text: message, timestamp: new Date().toLocaleTimeString() }; + setMessages(prev => [entry, ...prev]); + setInput(''); + sendSignal(`Intent captured: ${message}`); + if (controlMode === 'control' && message.length > 20) { + queueIntent(message); + } else { + const ws = getWebSocket(); + ws.send({ type: 'chat', message, channel: 'command' }); + setTimeout(() => { + setMessages(prev => [{ id: `${Date.now()}-assistant`, role: 'assistant', text: 'Intent accepted. Routing through policy lattice for plan negotiation.', timestamp: new Date().toLocaleTimeString() }, ...prev]); + }, 700); + } +======= const { controlMode, sendSignal } = useWebModeContext(); const [input, setInput] = useState(''); const [messages, setMessages] = useState([ @@ -59,6 +94,7 @@ export function CommandPanel() { ...prev ]); }, 700); +>>>>>>> main }; return ( @@ -68,16 +104,52 @@ export function CommandPanel() { Chat-First Control +<<<<<<< HEAD +
+ {controlMode} access +
+ {intentQueue.length > 0 && ( +
+ {intentQueue.filter((i: any) => i.status !== 'ready').length} analyzing +
+ )} +=======
{controlMode} access
+>>>>>>> main
+<<<<<<< HEAD + {intentQueue.length > 0 && ( +
+
Intent Fabrication
+ {intentQueue.slice(-3).map((draft: any) => ( +
+
+ {draft.text} + {draft.status} +
+
+ Conf: {Math.round(draft.confidence * 100)}% + {draft.predictedOutcome || 'Simulating...'} +
+ {(draft.status === 'analyzing' || draft.status === 'policy-check') && ( +
+ )} +
+ ))} +
+ )} +
+ {messages.map(message => ( +
+=======
@@ -91,6 +163,7 @@ export function CommandPanel() { ? 'bg-neuro-bg/70 border-neuro-border/60 text-neuro-text-primary' : 'bg-gradient-to-r from-cyan-500/20 to-blue-500/10 border-cyan-400/30 text-neuro-text-primary' )}> +>>>>>>> main
{message.role === 'assistant' ? 'System' : 'User'} {message.timestamp} @@ -100,12 +173,26 @@ export function CommandPanel() { ))}
+<<<<<<< HEAD +======= +>>>>>>> main

Intent Presets

{COMMAND_PRESETS.map(preset => ( +<<<<<<< HEAD + + ))} +
+
+
+

Safety Constraints

+
+
Multi-stage intent arbitration active.
+
Policy lattice enforcement enabled.
+=======
+>>>>>>> main
+<<<<<<< HEAD + setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && submitMessage(input)} placeholder="Transmit intent, directive, or negotiation request..." className="flex-1 rounded-xl border border-neuro-border/60 bg-neuro-bg/80 px-4 py-3 text-sm text-neuro-text-primary placeholder:text-neuro-text-muted focus:outline-none focus:ring-1 focus:ring-cyan-400/50" /> + + +======= setInput(event.target.value)} @@ -150,6 +243,7 @@ export function CommandPanel() { > +>>>>>>> main
); diff --git a/web-ui/src/components/webmode/panels/ConfigMatrixPanel.tsx b/web-ui/src/components/webmode/panels/ConfigMatrixPanel.tsx index 2bc0293..4057b5c 100644 --- a/web-ui/src/components/webmode/panels/ConfigMatrixPanel.tsx +++ b/web-ui/src/components/webmode/panels/ConfigMatrixPanel.tsx @@ -1,6 +1,31 @@ 'use client'; import { useMemo } from 'react'; +<<<<<<< HEAD +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +import { configSchema } from '@/lib/webmode/config-schema'; +import { AlertTriangle, Lock, RefreshCw } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +function getValue(source: any, path: string) { + if (!source) return undefined; + return path.split('.').reduce((acc: any, key: string) => (acc ? acc[key] : undefined), source); +} + +export function ConfigMatrixPanel() { + const { config, updateConfig, controlMode } = useWebModeContext(); + + const conflicts = useMemo(() => { + const list: string[] = []; + if (config.stealth?.enabled && config.neurorift?.sandboxLevel === 'hardened') { + list.push('Warning: Stealth Mode optimal with Sealed sandbox (currently Hardened).'); + } + if (config.aiBehavior?.selfEvolution && config.agentModes?.supervision === 'strict') { + list.push('Constraint: Self-Evolution dampened by Strict supervision.'); + } + return list; + }, [config]); +======= import { configSchema } from '@/lib/webmode/config-schema'; import { useWebModeContext } from '@/components/webmode/WebModeProvider'; @@ -10,11 +35,62 @@ function getValue(source: any, path: string) { export function ConfigMatrixPanel() { const { config, updateConfig } = useWebModeContext(); +>>>>>>> main const sections = useMemo(() => configSchema, []); return (
+<<<<<<< HEAD + {conflicts.length > 0 && ( +
+
+ + Policy Conflicts Detected +
+ {conflicts.map((c, i) => ( +
{c}
+ ))} +
+ )} + + {sections.map(section => ( +
+
+
+

{section.title}

+

{section.description}

+
+ {controlMode === 'read' && } +
+ +
+ {section.fields.map(field => { + const value = getValue(config, field.path); + const isLocked = controlMode === 'read'; + return ( +
+
+ {field.label}{isLocked && } + {typeof value === 'number' ? value.toFixed(2) : String(value)} +
+ + {field.type === 'slider' && ( +
+ updateConfig(field.path, Number(e.target.value))} disabled={isLocked} className="w-full h-1 bg-neuro-border/50 rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-cyan-400 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:shadow-[0_0_10px_cyan]" /> +
+
+ )} + + {field.type === 'toggle' && ( + + )} + + {field.type === 'select' && ( + )} @@ -68,6 +145,14 @@ export function ConfigMatrixPanel() { ); })}
+<<<<<<< HEAD + +
+ Policy Compilation + HASH: {Math.random().toString(16).slice(2, 8).toUpperCase()} +
+======= +>>>>>>> main
))}
diff --git a/web-ui/src/components/webmode/panels/ExecutionTimelinePanel.tsx b/web-ui/src/components/webmode/panels/ExecutionTimelinePanel.tsx index 23d10fa..654ffa1 100644 --- a/web-ui/src/components/webmode/panels/ExecutionTimelinePanel.tsx +++ b/web-ui/src/components/webmode/panels/ExecutionTimelinePanel.tsx @@ -1,5 +1,34 @@ 'use client'; +<<<<<<< HEAD +import { Clock } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const TIMELINE = [ + { id: 1, label: 'Boot Sequence', status: 'complete', time: '0ms' }, + { id: 2, label: 'Bus Sync', status: 'complete', time: '200ms' }, + { id: 3, label: 'Agent Registration', status: 'complete', time: '450ms' }, + { id: 4, label: 'Policy Lattice Compile', status: 'active', time: '800ms' }, + { id: 5, label: 'Intent Router Online', status: 'pending', time: '—' }, +]; + +export function ExecutionTimelinePanel() { + return ( +
+
+ + Execution Timeline +
+
+ {TIMELINE.map(step => ( +
+
+ {step.label} + {step.time} +
+ ))} +
+======= import { useNeuroRift } from '@/lib/hooks'; import { cn } from '@/lib/utils'; @@ -29,6 +58,7 @@ export function ExecutionTimelinePanel() {
))} +>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/IntentFlowPanel.tsx b/web-ui/src/components/webmode/panels/IntentFlowPanel.tsx index e924c95..31dde87 100644 --- a/web-ui/src/components/webmode/panels/IntentFlowPanel.tsx +++ b/web-ui/src/components/webmode/panels/IntentFlowPanel.tsx @@ -1,5 +1,18 @@ 'use client'; +<<<<<<< HEAD +import { Workflow } from 'lucide-react'; + +export function IntentFlowPanel() { + return ( +
+
+ + Intent Flow +
+
+ Submit an intent to visualize the negotiation flow. +======= import { ArrowRight, Shield, Wand2, Layers } from 'lucide-react'; const steps = [ @@ -30,6 +43,7 @@ export function IntentFlowPanel() {
Intent routing is declarative and policy-driven. Execution is triggered only after negotiation, scoring, and enforcement checks succeed. +>>>>>>> main
); diff --git a/web-ui/src/components/webmode/panels/ManualToolPanel.tsx b/web-ui/src/components/webmode/panels/ManualToolPanel.tsx new file mode 100644 index 0000000..614846a --- /dev/null +++ b/web-ui/src/components/webmode/panels/ManualToolPanel.tsx @@ -0,0 +1,124 @@ + +import React, { useState, useEffect, useRef } from 'react'; +import { useWebModeContext } from '../WebModeProvider'; +import { Play, Square, Terminal, Loader2 } from 'lucide-react'; + +export function ManualToolPanel() { + const { adapter } = useWebModeContext(); + const [selectedTool, setSelectedTool] = useState('nmap'); + const [args, setArgs] = useState(''); + const [isRunning, setIsRunning] = useState(false); + const [executionId, setExecutionId] = useState(null); + const [output, setOutput] = useState([]); + const outputRef = useRef(null); + + const tools = ['nmap', 'nuclei', 'subfinder', 'ffuf', 'custom']; + + const handleRun = async () => { + try { + setIsRunning(true); + setOutput([]); + const id = await adapter.runTool(selectedTool, args); + setExecutionId(id); + } catch (e) { + setOutput(prev => [...prev, `Error starting tool: ${e}`]); + setIsRunning(false); + } + }; + + const handleStop = async () => { + if (executionId) { + await adapter.cancelTool(executionId); + setIsRunning(false); + setOutput(prev => [...prev, '\n[Stopped by user]']); + } + }; + + useEffect(() => { + if (!isRunning || !executionId) return; + + const interval = setInterval(async () => { + try { + const status = await adapter.getToolStatus(executionId); + setOutput([...status.stdout, ...status.stderr]); + + if (status.status !== 'running') { + setIsRunning(false); + setExecutionId(null); + } + } catch (e) { + console.error("Failed to poll status", e); + } + }, 1000); + + return () => clearInterval(interval); + }, [isRunning, executionId, adapter]); + + useEffect(() => { + if (outputRef.current) { + outputRef.current.scrollTop = outputRef.current.scrollHeight; + } + }, [output]); + + return ( +
+
+
+ + MANUAL OPERATOR PLANE +
+ {isRunning && EXECUTING} +
+ +
+
+ + setArgs(e.target.value)} + placeholder="--flags target" + disabled={isRunning} + className="flex-1 bg-black/50 border border-white/20 rounded px-3 py-1.5 text-sm font-mono text-cyan-100 focus:outline-none focus:border-cyan-500/50 placeholder-white/20" + /> + {!isRunning ? ( + + ) : ( + + )} +
+ +
+ {output.length === 0 ? ( + Ready for input... + ) : ( + output.map((line, i) => ( +
{line}
+ )) + )} +
+
+
+ ); +} diff --git a/web-ui/src/components/webmode/panels/MemoryPulsePanel.tsx b/web-ui/src/components/webmode/panels/MemoryPulsePanel.tsx index ebf1fc9..60dc608 100644 --- a/web-ui/src/components/webmode/panels/MemoryPulsePanel.tsx +++ b/web-ui/src/components/webmode/panels/MemoryPulsePanel.tsx @@ -1,5 +1,98 @@ 'use client'; +<<<<<<< HEAD +import { useState, useEffect } from 'react'; +import { Brain, TrendingUp, TrendingDown, Minus, AlertTriangle } from 'lucide-react'; +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +import { cn } from '@/lib/utils'; + +interface MemoryDisplay { + id: string; + label: string; + value: number; + trend: 'up' | 'down' | 'stable'; + type: string; + decayRate: number; + barColor: string; + icon: string; +} + +const DEFAULT_METRICS: MemoryDisplay[] = [ + { id: 'episodic', label: 'Episodic Memory', value: 0.72, trend: 'down', type: 'episodic', decayRate: 0.03, barColor: 'bg-gradient-to-r from-violet-500 to-fuchsia-500', icon: '🧠' }, + { id: 'working', label: 'Working Context', value: 0.89, trend: 'up', type: 'preference', decayRate: 0.01, barColor: 'bg-gradient-to-r from-cyan-400 to-blue-500', icon: '⚡' }, + { id: 'prefs', label: 'Preferences', value: 0.45, trend: 'stable', type: 'reinforcement', decayRate: 0.005, barColor: 'bg-gradient-to-r from-emerald-500 to-teal-400', icon: '🎯' }, +]; + +const TrendIcon = ({ trend }: { trend: string }) => { + if (trend === 'up') return ; + if (trend === 'down') return ; + return ; +}; + +export function MemoryPulsePanel() { + const { config } = useWebModeContext(); + const [metrics, setMetrics] = useState(DEFAULT_METRICS); + const [cadence, setCadence] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setMetrics(prev => prev.map(m => ({ + ...m, + value: Math.max(0.05, Math.min(0.99, m.value + (Math.random() - 0.52) * m.decayRate * 2)), + }))); + setCadence(p => (p + 1) % 4); + }, 2500); + return () => clearInterval(interval); + }, []); + + return ( +
+
+
+ + Memory Pulse +
+
+ {[0, 1, 2, 3].map(i => ( +
+ ))} +
+
+ +
+ {metrics.map(metric => ( +
+
+
+ {metric.icon} + {metric.label} +
+
+ + {Math.round(metric.value * 100)}% +
+
+ + {/* Progress Bar */} +
+
+
+
+
+ + {/* Details on hover */} +
+
+ Decay: {(metric.decayRate * 100).toFixed(1)}%/cycle + {metric.value > 0.85 && ( + High Influence + )} +
+
+
+ ))} +
+======= import { useWebModeContext } from '@/components/webmode/WebModeProvider'; export function MemoryPulsePanel() { @@ -29,6 +122,7 @@ export function MemoryPulsePanel() {
))}

Memory is local-only and influences planning without transmission.

+>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/OnlineModePanel.tsx b/web-ui/src/components/webmode/panels/OnlineModePanel.tsx index 902d977..58d9574 100644 --- a/web-ui/src/components/webmode/panels/OnlineModePanel.tsx +++ b/web-ui/src/components/webmode/panels/OnlineModePanel.tsx @@ -1,20 +1,105 @@ 'use client'; +<<<<<<< HEAD +import { useEffect, useState } from 'react'; +import { Globe, ShieldAlert, ShieldCheck } from 'lucide-react'; +======= import { useMemo } from 'react'; import { Globe, ShieldAlert } from 'lucide-react'; +>>>>>>> main import { useWebModeContext } from '@/components/webmode/WebModeProvider'; import { cn } from '@/lib/utils'; export function OnlineModePanel() { +<<<<<<< HEAD + const { config, updateConfig, controlMode } = useWebModeContext(); + const [latency, setLatency] = useState(12); + + useEffect(() => { + if (!config.onlineMode.enabled) return; + const interval = setInterval(() => { + setLatency((prev: number) => Math.max(8, Math.min(45, prev + (Math.random() - 0.5) * 5))); + }, 2000); + return () => clearInterval(interval); +======= const { config, updateConfig } = useWebModeContext(); const publicUrl = useMemo(() => { if (!config.onlineMode.enabled) return null; return `https://nr-${Math.floor(Math.random() * 9999)}.tunnel.neurorift.ai`; +>>>>>>> main }, [config.onlineMode.enabled]); return (
+<<<<<<< HEAD + {/* Header Toggle */} +
+
+
+ +
+
+
Secure Tunnel
+
{config.onlineMode.enabled ? 'Encrypted Link Active' : 'Offline / Isolated'}
+
+
+ +
+ + {config.onlineMode.enabled && ( +
+ {/* Tunnel Visualization */} +
+
+
+ +
+ Local +
+ +
+
+ AES-256 • {Math.round(latency)}ms +
+
+ +
+
+ +
+ Remote +
+
+ + {/* Scopes & Access */} +
+
+
Exposure Scopes
+ {['API Access', 'Chat Interface', 'Full Dashboard'].map(scope => ( +
+ {scope} +
+
+ ))} +
+
+
Access Policy
+ +
+ + MFA enforcement recommended for remote admin sessions. +
+
+
+
+ )} +=======
@@ -57,6 +142,7 @@ export function OnlineModePanel() { Remote exposure requires explicit approval and never bypasses NeuroRift enforcement.
+>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/PermissionsPanel.tsx b/web-ui/src/components/webmode/panels/PermissionsPanel.tsx index 7a6d3f0..47d0036 100644 --- a/web-ui/src/components/webmode/panels/PermissionsPanel.tsx +++ b/web-ui/src/components/webmode/panels/PermissionsPanel.tsx @@ -1,5 +1,106 @@ 'use client'; +<<<<<<< HEAD +import { useState } from 'react'; +import { ShieldCheck, CheckCircle, XCircle, AlertTriangle } from 'lucide-react'; +import { useWebModeContext } from '@/components/webmode/WebModeProvider'; +import { cn } from '@/lib/utils'; + +const MOCK_PERMISSIONS = [ + { + id: 'perm-1', title: 'Scope Expansion: External DNS', description: 'Agent requests access to external DNS resolution for reconnaissance.', + riskLevel: 'medium' as const, origin: 'operator', status: 'pending' as const, stage: 1, totalStages: 2, + policyDelta: '+DNS:external.resolve → operator.network', + }, + { + id: 'perm-2', title: 'Config Mutation: Sandbox Level', description: 'Self-evolution engine proposes sandbox relaxation for model fine-tuning.', + riskLevel: 'high' as const, origin: 'evolution-engine', status: 'pending' as const, stage: 1, totalStages: 3, + policyDelta: 'neurorift.sandboxLevel: hardened → strict', + }, +]; + +const RISK_COLORS: Record = { + low: 'text-emerald-400 border-emerald-500/30', + medium: 'text-amber-400 border-amber-500/30', + high: 'text-rose-400 border-rose-500/30', + critical: 'text-red-500 border-red-500/50', +}; + +export function PermissionsPanel() { + const { resolvePermission, permissionQueue } = useWebModeContext(); + const [enforcementFlash, setEnforcementFlash] = useState(null); + + const displayPerms = permissionQueue.length > 0 ? permissionQueue : MOCK_PERMISSIONS; + + const handleAction = (id: string, approved: boolean) => { + setEnforcementFlash(id); + setTimeout(() => { + resolvePermission(id, approved); + setEnforcementFlash(null); + }, 600); + }; + + return ( +
+
+ + Permission Decisions + {displayPerms.length > 0 && ( + {displayPerms.length} pending + )} +
+ +
+ {displayPerms.map(req => ( +
+ {enforcementFlash === req.id && ( +
+ )} +
+
+
{req.title}
+

{req.description}

+
+ {req.riskLevel} +
+ + {/* Risk Vectors */} +
+
Risk Vectors
+
+ Scope Expansion + Config Mutation +
+
+ + {/* Policy Delta */} + {req.policyDelta && ( +
{req.policyDelta}
+ )} + + {/* Stage + Actions */} +
+
+ {Array.from({ length: req.totalStages }).map((_, i) => ( +
+ ))} + {req.stage}/{req.totalStages} +
+
+ + +
+
+
+ ))} +
+======= import { useNeuroRift } from '@/lib/hooks'; import { cn } from '@/lib/utils'; @@ -27,6 +128,7 @@ export function PermissionsPanel() {
))} +>>>>>>> main
); } diff --git a/web-ui/src/components/webmode/panels/SessionManagerPanel.tsx b/web-ui/src/components/webmode/panels/SessionManagerPanel.tsx new file mode 100644 index 0000000..d718141 --- /dev/null +++ b/web-ui/src/components/webmode/panels/SessionManagerPanel.tsx @@ -0,0 +1,100 @@ + +import React, { useState, useEffect } from 'react'; +import { useWebModeContext } from '../WebModeProvider'; +import { Folder, PlayCircle, Trash2, Database, RefreshCw } from 'lucide-react'; +import { Session } from '@/lib/webmode/adapter/interface'; + +export function SessionManagerPanel() { + const { adapter } = useWebModeContext(); + const [sessions, setSessions] = useState([]); + const [loading, setLoading] = useState(false); + + const fetchSessions = async () => { + setLoading(true); + try { + const data = await adapter.listSessions(); + setSessions(data); + } catch (e) { + console.error("Failed to load sessions", e); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchSessions(); + }, [adapter]); + + const handleLoad = async (id: string) => { + try { + await adapter.loadSession(id); + // Trigger a global refresh or notification if needed + } catch (e) { + console.error("Failed to load session", e); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm('Are you sure you want to delete this session?')) return; + try { + await adapter.deleteSession(id); + fetchSessions(); + } catch (e) { + console.error("Failed to delete", e); + } + }; + + return ( +
+
+
+ + SESSION MANAGER +
+ +
+ +
+ {sessions.length === 0 ? ( +
No active sessions found.
+ ) : ( + sessions.map(s => ( +
+
+
+ + {s.target} + {s.mode} +
+
+ {new Date(s.startTime).toLocaleDateString()} + + {s.toolCount} tools +
+
+ +
+ + +
+
+ )) + )} +
+
+ ); +} diff --git a/web-ui/src/lib/webmode/adapter/interface.ts b/web-ui/src/lib/webmode/adapter/interface.ts new file mode 100644 index 0000000..7af25e5 --- /dev/null +++ b/web-ui/src/lib/webmode/adapter/interface.ts @@ -0,0 +1,58 @@ + +import { WebModeConfig } from '../types'; + +export interface FileNode { + name: string; + type: 'file' | 'directory'; + path: string; + children?: FileNode[]; + size?: number; + lastModified?: string; +} + +export interface Session { + id: string; + target: string; + mode: string; + startTime: string; + status: 'active' | 'completed' | 'failed'; + toolCount: number; +} + +export interface ToolExecution { + id: string; + tool: string; + args: string; + status: 'running' | 'completed' | 'failed'; + startTime: number; + duration?: number; + stdout: string[]; + stderr: string[]; +} + +export interface Artifact { + id: string; + sessionId: string; + filename: string; + type: 'json' | 'markdown' | 'html' | 'text'; + content?: string; +} + +export interface WebModeAdapter { + // Session Management + listSessions(): Promise; + loadSession(id: string): Promise; + deleteSession(id: string): Promise; + + // Tool Execution + runTool(tool: string, args: string): Promise; // Returns execution ID + getToolStatus(executionId: string): Promise; + cancelTool(executionId: string): Promise; + + // Filesystem / Artifacts + listArtifacts(sessionId: string): Promise; + getArtifactContent(path: string): Promise; + + // System State + getSystemMetrics(): Promise<{ cpu: number; memory: number; network: { in: number; out: number } }>; +} diff --git a/web-ui/src/lib/webmode/adapter/prototype.ts b/web-ui/src/lib/webmode/adapter/prototype.ts new file mode 100644 index 0000000..5282ea0 --- /dev/null +++ b/web-ui/src/lib/webmode/adapter/prototype.ts @@ -0,0 +1,156 @@ + +import { WebModeAdapter, Session, ToolExecution, FileNode } from './interface'; + +/** + * PrototypeAdapter: Mock implementation for safe demos and UI development. + * Simulates backend behavior without actual execution. + */ +export class PrototypeAdapter implements WebModeAdapter { + private mockSessions: Session[] = [ + { + id: 'sess_proto_001', + target: '192.168.1.10', + mode: 'recon', + startTime: new Date(Date.now() - 3600000).toISOString(), + status: 'completed', + toolCount: 5 + }, + { + id: 'sess_proto_002', + target: 'example.com', + mode: 'scan', + startTime: new Date().toISOString(), + status: 'active', + toolCount: 2 + } + ]; + + private activeExecutions: Map = new Map(); + + async listSessions(): Promise { + return this.mockSessions; + } + + async loadSession(id: string): Promise { + console.log(`[Prototype] Loading session ${id}`); + return Promise.resolve(); + } + + async deleteSession(id: string): Promise { + console.log(`[Prototype] Deleting session ${id}`); + this.mockSessions = this.mockSessions.filter(s => s.id !== id); + return Promise.resolve(); + } + + async runTool(tool: string, args: string): Promise { + const id = `exec_${Date.now()}`; + const startTime = Date.now(); + + const execution: ToolExecution = { + id, + tool, + args, + status: 'running', + startTime, + stdout: [`[Prototype] Starting ${tool} ${args}...`], + stderr: [] + }; + + this.activeExecutions.set(id, execution); + + // Simulate output streaming + this.simulateToolOutput(id, tool); + + return id; + } + + private simulateToolOutput(id: string, tool: string) { + let step = 0; + const interval = setInterval(() => { + const exec = this.activeExecutions.get(id); + if (!exec || exec.status !== 'running') { + clearInterval(interval); + return; + } + + step++; + + // Mock output based on tool + const mockLines: Record = { + 'nmap': [ + `Starting Nmap 7.94 ( https://nmap.org ) at ${new Date().toISOString()}`, + `Nmap scan report for target (192.168.1.1)`, + `Host is up (0.0023s latency).`, + `Not shown: 998 closed tcp ports (conn-refused)`, + `PORT STATE SERVICE`, + `22/tcp open ssh`, + `80/tcp open http`, + `Nmap done: 1 IP address (1 host up) scanned in 4.23 seconds` + ], + 'default': [ + `[Process] Working... (${step}0%)`, + `[Process] Generating results...` + ] + }; + + const lines = mockLines[tool] || mockLines['default']; + const line = lines[step % lines.length]; + + if (line) { + exec.stdout.push(line); + } + + if (step >= 5 && Math.random() > 0.5) { + exec.status = 'completed'; + exec.duration = Date.now() - exec.startTime; + exec.stdout.push(`[Prototype] Execution completed successfully.`); + clearInterval(interval); + } + + this.activeExecutions.set(id, { ...exec }); // Trigger update? In a real app this would push to state or stream + }, 1000); + } + + async getToolStatus(executionId: string): Promise { + const exec = this.activeExecutions.get(executionId); + if (!exec) throw new Error("Execution not found"); + return { ...exec }; // Return copy + } + + async cancelTool(executionId: string): Promise { + const exec = this.activeExecutions.get(executionId); + if (exec) { + exec.status = 'failed'; + exec.stderr.push('[Prototype] Execution cancelled by user.'); + this.activeExecutions.set(executionId, exec); + } + } + + async listArtifacts(sessionId: string): Promise { + return [ + { + name: 'scans', + type: 'directory', + path: '/scans', + children: [ + { name: 'nmap_results.txt', type: 'file', path: '/scans/nmap_results.txt', size: 1024 }, + { name: 'nuclei_report.json', type: 'file', path: '/scans/nuclei_report.json', size: 2048 } + ] + }, + { name: 'summary.md', type: 'file', path: '/summary.md', size: 512 } + ]; + } + + async getArtifactContent(path: string): Promise { + return `[Prototype] Content for ${path}\n\nThis is simulated content for demonstration purposes.`; + } + + async getSystemMetrics() { + // Simulate fluctuating metrics + return { + cpu: 10 + Math.random() * 20, + memory: 30 + Math.random() * 10, + network: { in: Math.random() * 1000, out: Math.random() * 500 } + }; + } +} diff --git a/web-ui/src/lib/webmode/adapter/real.ts b/web-ui/src/lib/webmode/adapter/real.ts new file mode 100644 index 0000000..cfe469a --- /dev/null +++ b/web-ui/src/lib/webmode/adapter/real.ts @@ -0,0 +1,60 @@ + +import { WebModeAdapter, Session, ToolExecution, FileNode } from './interface'; + +/** + * RealAdapter: Production implementation connecting to Next.js API routes. + * These routes bridge to the Python backend (VulnForge/NeuroRift). + */ +export class RealAdapter implements WebModeAdapter { + + private async fetchJson(url: string, options?: RequestInit): Promise { + const res = await fetch(url, options); + if (!res.ok) { + throw new Error(`API Error: ${res.status} ${res.statusText}`); + } + return res.json(); + } + + async listSessions(): Promise { + return this.fetchJson('/api/sessions'); + } + + async loadSession(id: string): Promise { + await this.fetchJson(`/api/sessions/${id}/load`, { method: 'POST' }); + } + + async deleteSession(id: string): Promise { + await this.fetchJson(`/api/sessions/${id}`, { method: 'DELETE' }); + } + + async runTool(tool: string, args: string): Promise { + const res = await this.fetchJson<{ executionId: string }>('/api/tools/run', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tool, args }) + }); + return res.executionId; + } + + async getToolStatus(executionId: string): Promise { + return this.fetchJson(`/api/tools/status/${executionId}`); + } + + async cancelTool(executionId: string): Promise { + await this.fetchJson(`/api/tools/cancel/${executionId}`, { method: 'POST' }); + } + + async listArtifacts(sessionId: string): Promise { + return this.fetchJson(`/api/sessions/${sessionId}/artifacts`); + } + + async getArtifactContent(path: string): Promise { + const res = await fetch(`/api/fs/content?path=${encodeURIComponent(path)}`); + if (!res.ok) throw new Error("Failed to fetch content"); + return res.text(); + } + + async getSystemMetrics() { + return this.fetchJson<{ cpu: number; memory: number; network: { in: number; out: number } }>('/api/system/metrics'); + } +} diff --git a/web-ui/tailwind.config.js b/web-ui/tailwind.config.js new file mode 100644 index 0000000..3fbc658 --- /dev/null +++ b/web-ui/tailwind.config.js @@ -0,0 +1,31 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], + theme: { + extend: { + colors: { + 'neuro-bg': 'rgba(10, 12, 18, 1)', + 'neuro-surface': 'rgba(18, 22, 32, 1)', + 'neuro-border': 'rgba(45, 55, 72, 0.6)', + 'neuro-text-primary': 'rgba(226, 232, 240, 1)', + 'neuro-text-secondary': 'rgba(148, 163, 184, 1)', + 'neuro-text-muted': 'rgba(100, 116, 139, 1)', + }, + animation: { + 'progress': 'progress 1.5s ease-in-out infinite', + 'shimmer': 'shimmer 2s linear infinite', + }, + keyframes: { + progress: { + '0%': { transform: 'translateX(-100%)' }, + '100%': { transform: 'translateX(100%)' }, + }, + shimmer: { + '0%': { transform: 'translateX(-200%)' }, + '100%': { transform: 'translateX(200%)' }, + }, + }, + }, + }, + plugins: [], +}; diff --git a/web-ui/tsconfig.json b/web-ui/tsconfig.json index c400794..a9c8fb3 100644 --- a/web-ui/tsconfig.json +++ b/web-ui/tsconfig.json @@ -1,5 +1,23 @@ { "compilerOptions": { +<<<<<<< HEAD + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", +======= "target": "ES2020", "lib": [ "ES2020", @@ -16,6 +34,7 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, +>>>>>>> main "incremental": true, "plugins": [ { @@ -26,8 +45,12 @@ "@/*": [ "./src/*" ] +<<<<<<< HEAD + } +======= }, "isolatedModules": true +>>>>>>> main }, "include": [ "next-env.d.ts",