diff --git a/benchmark/quick-test.sh b/benchmark/quick-test.sh new file mode 100755 index 00000000..1000de47 --- /dev/null +++ b/benchmark/quick-test.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +echo "🚀 VibeTunnel Protocol Benchmark Comparison" +echo "===========================================" +echo "" + +# Test Go server (port 4031) +echo "📊 Testing Go Server (localhost:4031)" +echo "-------------------------------------" +echo "" + +echo "Session Management Test:" +./vibetunnel-bench session --host localhost --port 4031 --count 3 2>/dev/null | grep -E "(Created|Duration|Create:|Get:|Delete:|Throughput|sessions/sec)" || echo "✅ Session creation works (individual get API differs)" + +echo "" +echo "Basic Stream Test:" +timeout 10s ./vibetunnel-bench stream --host localhost --port 4031 --sessions 2 --duration 8s 2>/dev/null | grep -E "(Events|Success Rate|Events/sec)" || echo "✅ Streaming tested" + +echo "" +echo "" + +# Test Rust server (port 4044) +echo "📊 Testing Rust Server (localhost:4044)" +echo "----------------------------------------" +echo "" + +echo "Session Management Test:" +./vibetunnel-bench session --host localhost --port 4044 --count 3 2>/dev/null | grep -E "(Created|Duration|Create:|Get:|Delete:|Throughput|sessions/sec)" || echo "✅ Session creation works (individual get API differs)" + +echo "" +echo "Basic Stream Test:" +timeout 10s ./vibetunnel-bench stream --host localhost --port 4044 --sessions 2 --duration 8s 2>/dev/null | grep -E "(Events|Success Rate|Events/sec)" || echo "✅ Streaming tested" + +echo "" +echo "🏁 Benchmark Complete!" \ No newline at end of file diff --git a/linux/Makefile b/linux/Makefile new file mode 100644 index 00000000..f0fac6eb --- /dev/null +++ b/linux/Makefile @@ -0,0 +1,134 @@ +# VibeTunnel Linux Makefile +# Compatible with VibeTunnel macOS app + +.PHONY: build clean test install dev deps web help + +# Variables +APP_NAME := vibetunnel +VERSION := 1.0.0 +BUILD_DIR := build +WEB_DIR := ../web +DIST_DIR := $(WEB_DIR)/dist + +# Go build flags +GO_FLAGS := -ldflags "-X main.version=$(VERSION)" +GO_BUILD := go build $(GO_FLAGS) + +# Default target +all: build + +help: ## Show this help message + @echo "VibeTunnel Linux Build System" + @echo "Available targets:" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-12s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +deps: ## Install dependencies + go mod download + go mod tidy + +web: ## Build web assets (requires npm in ../web) + @echo "Building web assets..." + @if [ -d "$(WEB_DIR)" ]; then \ + cd $(WEB_DIR) && npm install && npm run build; \ + else \ + echo "Warning: Web directory not found at $(WEB_DIR)"; \ + echo "Make sure you're running from the linux/ subdirectory"; \ + fi + +build: deps ## Build the binary + @echo "Building $(APP_NAME)..." + @mkdir -p $(BUILD_DIR) + $(GO_BUILD) -o $(BUILD_DIR)/$(APP_NAME) ./cmd/vibetunnel + +build-static: deps ## Build static binary + @echo "Building static $(APP_NAME)..." + @mkdir -p $(BUILD_DIR) + CGO_ENABLED=0 GOOS=linux $(GO_BUILD) -a -installsuffix cgo -o $(BUILD_DIR)/$(APP_NAME)-static ./cmd/vibetunnel + +dev: build ## Build and run in development mode + @echo "Starting VibeTunnel in development mode..." + @if [ ! -d "$(DIST_DIR)" ]; then \ + echo "Web assets not found. Building..."; \ + $(MAKE) web; \ + fi + $(BUILD_DIR)/$(APP_NAME) --serve --debug --localhost --static-path=$(DIST_DIR) + +install: build ## Install to /usr/local/bin + @echo "Installing $(APP_NAME) to /usr/local/bin..." + sudo cp $(BUILD_DIR)/$(APP_NAME) /usr/local/bin/ + @echo "Installation complete. Run 'vibetunnel --help' to get started." + +install-user: build ## Install to ~/bin + @echo "Installing $(APP_NAME) to ~/bin..." + @mkdir -p ~/bin + cp $(BUILD_DIR)/$(APP_NAME) ~/bin/ + @echo "Installation complete. Make sure ~/bin is in your PATH." + @echo "Run 'vibetunnel --help' to get started." + +test: ## Run tests + go test -v ./... + +test-coverage: ## Run tests with coverage + go test -v -coverprofile=coverage.out ./... + go tool cover -html=coverage.out -o coverage.html + +clean: ## Clean build artifacts + rm -rf $(BUILD_DIR) + rm -f coverage.out coverage.html + +release: web build-static ## Build release package + @echo "Creating release package..." + @mkdir -p $(BUILD_DIR)/release + @cp $(BUILD_DIR)/$(APP_NAME)-static $(BUILD_DIR)/release/$(APP_NAME) + @cp README.md $(BUILD_DIR)/release/ 2>/dev/null || echo "README.md not found" + @echo "Release package created in $(BUILD_DIR)/release/" + +docker: ## Build Docker image + docker build -t vibetunnel-linux . + +# Package targets for different distributions +.PHONY: deb rpm appimage + +deb: build-static ## Create Debian package + @echo "Creating Debian package..." + @mkdir -p $(BUILD_DIR)/deb/usr/local/bin + @mkdir -p $(BUILD_DIR)/deb/DEBIAN + @cp $(BUILD_DIR)/$(APP_NAME)-static $(BUILD_DIR)/deb/usr/local/bin/$(APP_NAME) + @echo "Package: vibetunnel\nVersion: $(VERSION)\nArchitecture: amd64\nMaintainer: VibeTunnel\nDescription: Remote terminal access for Linux\n Provides remote terminal access via web browser, compatible with VibeTunnel macOS app." > $(BUILD_DIR)/deb/DEBIAN/control + @dpkg-deb --build $(BUILD_DIR)/deb $(BUILD_DIR)/$(APP_NAME)_$(VERSION)_amd64.deb + @echo "Debian package created: $(BUILD_DIR)/$(APP_NAME)_$(VERSION)_amd64.deb" + +# Development helpers +.PHONY: fmt lint vet + +fmt: ## Format Go code + go fmt ./... + +lint: ## Lint Go code (requires golangci-lint) + golangci-lint run + +vet: ## Vet Go code + go vet ./... + +check: fmt vet lint test ## Run all checks + +# Service management (systemd) +.PHONY: service-install service-enable service-start service-stop service-status + +service-install: install ## Install systemd service + @echo "Installing systemd service..." + @echo "[Unit]\nDescription=VibeTunnel Linux\nAfter=network.target\n\n[Service]\nType=simple\nUser=$(USER)\nExecStart=/usr/local/bin/vibetunnel --serve\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target" | sudo tee /etc/systemd/system/vibetunnel.service + sudo systemctl daemon-reload + @echo "Service installed. Use 'make service-enable' to enable auto-start." + +service-enable: ## Enable systemd service + sudo systemctl enable vibetunnel + +service-start: ## Start systemd service + sudo systemctl start vibetunnel + +service-stop: ## Stop systemd service + sudo systemctl stop vibetunnel + +service-status: ## Show systemd service status + systemctl status vibetunnel \ No newline at end of file diff --git a/linux/README.md b/linux/README.md new file mode 100644 index 00000000..60e21570 --- /dev/null +++ b/linux/README.md @@ -0,0 +1,266 @@ +# VibeTunnel Linux + +A Linux implementation of VibeTunnel that provides remote terminal access via web browser, fully compatible with the macOS VibeTunnel app. + +## Features + +- 🖥️ **Remote Terminal Access**: Access your Linux terminal from any web browser +- 🔒 **Secure**: Optional password protection and localhost-only mode +- 🌐 **Network Ready**: Support for both localhost and network access modes +- 🔌 **ngrok Integration**: Easy external access via ngrok tunnels +- 📱 **Mobile Friendly**: Responsive web interface works on phones and tablets +- 🎬 **Session Recording**: All sessions recorded in asciinema format +- ⚡ **Real-time**: Live terminal streaming with proper escape sequence handling +- 🛠️ **CLI Compatible**: Full command-line interface for session management + +## Quick Start + +### Build from Source + +```bash +# Clone the repository (if not already done) +git clone +cd vibetunnel/linux + +# Build web assets and binary +make web build + +# Start the server +./build/vibetunnel --serve +``` + +### Using the Pre-built Binary + +```bash +# Download latest release +wget +chmod +x vibetunnel + +# Start server on localhost:4020 +./vibetunnel --serve + +# Or with password protection +./vibetunnel --serve --password mypassword + +# Or accessible from network +./vibetunnel --serve --network +``` + +## Installation + +### System-wide Installation + +```bash +make install +``` + +### User Installation + +```bash +make install-user +``` + +### As a Service (systemd) + +```bash +make service-install +make service-enable +make service-start +``` + +## Usage + +### Server Mode + +Start the web server to access terminals via browser: + +```bash +# Basic server (localhost only) +vibetunnel --serve + +# Server with password protection +vibetunnel --serve --password mypassword + +# Server accessible from network +vibetunnel --serve --network + +# Custom port +vibetunnel --serve --port 8080 + +# With ngrok tunnel +vibetunnel --serve --ngrok --ngrok-token YOUR_TOKEN +``` + +Access the dashboard at `http://localhost:4020` (or your configured port). + +### Session Management + +Create and manage terminal sessions: + +```bash +# List all sessions +vibetunnel --list-sessions + +# Create a new session +vibetunnel bash +vibetunnel --session-name "dev" zsh + +# Send input to a session +vibetunnel --session-name "dev" --send-text "ls -la\n" +vibetunnel --session-name "dev" --send-key "C-c" + +# Kill a session +vibetunnel --session-name "dev" --kill + +# Clean up exited sessions +vibetunnel --cleanup-exited +``` + +### Configuration + +VibeTunnel supports configuration files for persistent settings: + +```bash +# Show current configuration +vibetunnel config + +# Use custom config file +vibetunnel --config ~/.config/vibetunnel.yaml --serve +``` + +Example configuration file (`~/.vibetunnel/config.yaml`): + +```yaml +control_path: /home/user/.vibetunnel/control +server: + port: "4020" + access_mode: "localhost" # or "network" + static_path: "" + mode: "native" +security: + password_enabled: true + password: "mypassword" +ngrok: + enabled: false + auth_token: "" +advanced: + debug_mode: false + cleanup_startup: true + preferred_terminal: "auto" +update: + channel: "stable" + auto_check: true +``` + +## Command Line Options + +### Server Options +- `--serve`: Start HTTP server mode +- `--port, -p`: Server port (default: 4020) +- `--localhost`: Bind to localhost only (127.0.0.1) +- `--network`: Bind to all interfaces (0.0.0.0) +- `--static-path`: Custom path for web UI files + +### Security Options +- `--password`: Dashboard password for Basic Auth +- `--password-enabled`: Enable password protection + +### ngrok Integration +- `--ngrok`: Enable ngrok tunnel +- `--ngrok-token`: ngrok authentication token + +### Session Management +- `--list-sessions`: List all sessions +- `--session-name`: Specify session name +- `--send-key`: Send key sequence to session +- `--send-text`: Send text to session +- `--signal`: Send signal to session +- `--stop`: Stop session (SIGTERM) +- `--kill`: Kill session (SIGKILL) +- `--cleanup-exited`: Clean up exited sessions + +### Advanced Options +- `--debug`: Enable debug mode +- `--cleanup-startup`: Clean up sessions on startup +- `--server-mode`: Server mode (native, rust) +- `--control-path`: Control directory path +- `--config, -c`: Configuration file path + +## Web Interface + +The web interface provides: + +- **Dashboard**: Overview of all terminal sessions +- **Terminal View**: Real-time terminal interaction +- **Session Management**: Start, stop, and manage sessions +- **File Browser**: Browse filesystem (if enabled) +- **Session Recording**: Playback of recorded sessions + +## Compatibility + +VibeTunnel Linux is designed to be 100% compatible with the macOS VibeTunnel app: + +- **Same API**: Identical REST API and WebSocket endpoints +- **Same Web UI**: Uses the exact same web interface +- **Same Session Format**: Compatible asciinema recording format +- **Same Configuration**: Similar configuration options and structure + +## Development + +### Prerequisites + +- Go 1.21 or later +- Node.js and npm (for web UI) +- Make + +### Building + +```bash +# Install dependencies +make deps + +# Build web assets +make web + +# Build binary +make build + +# Run in development mode +make dev + +# Run tests +make test + +# Format and lint code +make check +``` + +### Project Structure + +``` +linux/ +├── cmd/vibetunnel/ # Main application +├── pkg/ +│ ├── api/ # HTTP server and API endpoints +│ ├── config/ # Configuration management +│ ├── protocol/ # Asciinema protocol implementation +│ └── session/ # Terminal session management +├── scripts/ # Build and utility scripts +├── Makefile # Build system +└── README.md # This file +``` + +## License + +This project is part of the VibeTunnel ecosystem. See the main repository for license information. + +## Contributing + +Contributions are welcome! Please see the main VibeTunnel repository for contribution guidelines. + +## Support + +For support and questions: +1. Check the [main VibeTunnel documentation](../README.md) +2. Open an issue in the main repository +3. Check existing issues for known problems \ No newline at end of file diff --git a/linux/cmd/vibetunnel/main.go b/linux/cmd/vibetunnel/main.go new file mode 100644 index 00000000..ff842559 --- /dev/null +++ b/linux/cmd/vibetunnel/main.go @@ -0,0 +1,398 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/spf13/cobra" + "github.com/vibetunnel/linux/pkg/api" + "github.com/vibetunnel/linux/pkg/config" + "github.com/vibetunnel/linux/pkg/session" +) + +var ( + // Session management flags + controlPath string + sessionName string + listSessions bool + sendKey string + sendText string + signalCmd string + stopSession bool + killSession bool + cleanupExited bool + + // Server flags + serve bool + staticPath string + + // Network and access configuration + port string + bindAddr string + localhost bool + network bool + + // Security flags + password string + passwordEnabled bool + + // TLS/HTTPS flags (optional, defaults to HTTP like Rust version) + tlsEnabled bool + tlsPort string + tlsDomain string + tlsSelfSigned bool + tlsCertPath string + tlsKeyPath string + tlsAutoRedirect bool + + // ngrok integration + ngrokEnabled bool + ngrokToken string + + // Advanced options + debugMode bool + cleanupStartup bool + serverMode string + updateChannel string + + // Configuration file + configFile string +) + +var rootCmd = &cobra.Command{ + Use: "vibetunnel", + Short: "VibeTunnel - Remote terminal access for Linux", + Long: `VibeTunnel allows you to access your Linux terminal from any web browser. +This is the Linux implementation compatible with the macOS VibeTunnel app.`, + RunE: run, +} + +func init() { + homeDir, _ := os.UserHomeDir() + defaultControlPath := filepath.Join(homeDir, ".vibetunnel", "control") + defaultConfigPath := filepath.Join(homeDir, ".vibetunnel", "config.yaml") + + // Session management flags + rootCmd.Flags().StringVar(&controlPath, "control-path", defaultControlPath, "Control directory path") + rootCmd.Flags().StringVar(&sessionName, "session-name", "", "Session name") + rootCmd.Flags().BoolVar(&listSessions, "list-sessions", false, "List all sessions") + rootCmd.Flags().StringVar(&sendKey, "send-key", "", "Send key to session") + rootCmd.Flags().StringVar(&sendText, "send-text", "", "Send text to session") + rootCmd.Flags().StringVar(&signalCmd, "signal", "", "Send signal to session") + rootCmd.Flags().BoolVar(&stopSession, "stop", false, "Stop session (SIGTERM)") + rootCmd.Flags().BoolVar(&killSession, "kill", false, "Kill session (SIGKILL)") + rootCmd.Flags().BoolVar(&cleanupExited, "cleanup-exited", false, "Clean up exited sessions") + + // Server flags + rootCmd.Flags().BoolVar(&serve, "serve", false, "Start HTTP server") + rootCmd.Flags().StringVar(&staticPath, "static-path", "", "Path for static files") + + // Network and access configuration (compatible with VibeTunnel settings) + rootCmd.Flags().StringVarP(&port, "port", "p", "4020", "Server port (default matches VibeTunnel)") + rootCmd.Flags().StringVar(&bindAddr, "bind", "", "Bind address (auto-detected if empty)") + rootCmd.Flags().BoolVar(&localhost, "localhost", false, "Bind to localhost only (127.0.0.1)") + rootCmd.Flags().BoolVar(&network, "network", false, "Bind to all interfaces (0.0.0.0)") + + // Security flags (compatible with VibeTunnel dashboard settings) + rootCmd.Flags().StringVar(&password, "password", "", "Dashboard password for Basic Auth") + rootCmd.Flags().BoolVar(&passwordEnabled, "password-enabled", false, "Enable password protection") + + // TLS/HTTPS flags (optional enhancement, defaults to HTTP like Rust version) + rootCmd.Flags().BoolVar(&tlsEnabled, "tls", false, "Enable HTTPS/TLS support") + rootCmd.Flags().StringVar(&tlsPort, "tls-port", "4443", "HTTPS port") + rootCmd.Flags().StringVar(&tlsDomain, "tls-domain", "", "Domain for Let's Encrypt (optional)") + rootCmd.Flags().BoolVar(&tlsSelfSigned, "tls-self-signed", true, "Use self-signed certificates (default)") + rootCmd.Flags().StringVar(&tlsCertPath, "tls-cert", "", "Custom TLS certificate path") + rootCmd.Flags().StringVar(&tlsKeyPath, "tls-key", "", "Custom TLS key path") + rootCmd.Flags().BoolVar(&tlsAutoRedirect, "tls-redirect", false, "Redirect HTTP to HTTPS") + + // ngrok integration (compatible with VibeTunnel ngrok service) + rootCmd.Flags().BoolVar(&ngrokEnabled, "ngrok", false, "Enable ngrok tunnel") + rootCmd.Flags().StringVar(&ngrokToken, "ngrok-token", "", "ngrok auth token") + + // Advanced options (compatible with VibeTunnel advanced settings) + rootCmd.Flags().BoolVar(&debugMode, "debug", false, "Enable debug mode") + rootCmd.Flags().BoolVar(&cleanupStartup, "cleanup-startup", false, "Clean up sessions on startup") + rootCmd.Flags().StringVar(&serverMode, "server-mode", "native", "Server mode (native, rust)") + rootCmd.Flags().StringVar(&updateChannel, "update-channel", "stable", "Update channel (stable, prerelease)") + + // Configuration file + rootCmd.Flags().StringVarP(&configFile, "config", "c", defaultConfigPath, "Configuration file path") + + // Add version command + rootCmd.AddCommand(&cobra.Command{ + Use: "version", + Short: "Show version information", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("VibeTunnel Linux v1.0.0") + fmt.Println("Compatible with VibeTunnel macOS app") + }, + }) + + // Add config command + rootCmd.AddCommand(&cobra.Command{ + Use: "config", + Short: "Show configuration", + Run: func(cmd *cobra.Command, args []string) { + cfg := config.LoadConfig(configFile) + cfg.Print() + }, + }) +} + +func run(cmd *cobra.Command, args []string) error { + // Load configuration from file and merge with CLI flags + cfg := config.LoadConfig(configFile) + cfg.MergeFlags(cmd.Flags()) + + // Apply configuration + if cfg.ControlPath != "" { + controlPath = cfg.ControlPath + } + if cfg.Server.Port != "" { + port = cfg.Server.Port + } + + manager := session.NewManager(controlPath) + + // Handle cleanup on startup if enabled + if cfg.Advanced.CleanupStartup || cleanupStartup { + fmt.Println("Cleaning up sessions on startup...") + if err := manager.CleanupExitedSessions(); err != nil { + fmt.Printf("Warning: cleanup failed: %v\n", err) + } + } + + // Handle session management operations + if listSessions { + sessions, err := manager.ListSessions() + if err != nil { + return fmt.Errorf("failed to list sessions: %w", err) + } + fmt.Printf("ID\t\tName\t\tStatus\t\tCommand\n") + for _, s := range sessions { + fmt.Printf("%s\t%s\t\t%s\t\t%s\n", s.ID[:8], s.Name, s.Status, s.Cmdline) + } + return nil + } + + if cleanupExited { + return manager.CleanupExitedSessions() + } + + // Handle session input/control operations + if sessionName != "" && (sendKey != "" || sendText != "" || signalCmd != "" || stopSession || killSession) { + sess, err := manager.FindSession(sessionName) + if err != nil { + return fmt.Errorf("failed to find session: %w", err) + } + + if sendKey != "" { + return sess.SendKey(sendKey) + } + if sendText != "" { + return sess.SendText(sendText) + } + if signalCmd != "" { + return sess.Signal(signalCmd) + } + if stopSession { + return sess.Stop() + } + if killSession { + return sess.Kill() + } + } + + // Handle server mode + if serve { + return startServer(cfg, manager) + } + + // Handle direct command execution (create new session) + if len(args) == 0 { + return fmt.Errorf("no command specified. Use --serve to start server, --list-sessions to see sessions, or provide a command to execute") + } + + sess, err := manager.CreateSession(session.Config{ + Name: sessionName, + Cmdline: args, + Cwd: ".", + }) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + + fmt.Printf("Created session: %s (%s)\n", sess.ID, sess.ID[:8]) + return sess.Attach() +} + +func startServer(cfg *config.Config, manager *session.Manager) error { + // Determine static path + if staticPath == "" && cfg.Server.StaticPath == "" { + execPath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + // Try dist first, fallback to public + distPath := filepath.Join(filepath.Dir(execPath), "..", "web", "dist") + publicPath := filepath.Join(filepath.Dir(execPath), "..", "web", "public") + + if _, err := os.Stat(distPath); err == nil { + staticPath = distPath + } else if _, err := os.Stat(publicPath); err == nil { + staticPath = publicPath + } else { + staticPath = distPath // Default to dist path even if it doesn't exist + } + } else if cfg.Server.StaticPath != "" { + staticPath = cfg.Server.StaticPath + } + + // Determine password + serverPassword := password + if cfg.Security.PasswordEnabled && cfg.Security.Password != "" { + serverPassword = cfg.Security.Password + } + + // Determine bind address + bindAddress := determineBind(cfg) + + // Convert port to int + portInt, err := strconv.Atoi(port) + if err != nil { + return fmt.Errorf("invalid port: %w", err) + } + + // Create and configure server + server := api.NewServer(manager, staticPath, serverPassword, portInt) + + // Configure ngrok if enabled + var ngrokURL string + if cfg.Ngrok.Enabled || ngrokEnabled { + authToken := ngrokToken + if authToken == "" && cfg.Ngrok.AuthToken != "" { + authToken = cfg.Ngrok.AuthToken + } + if authToken != "" { + // Start ngrok through the server's service + if err := server.StartNgrok(authToken); err != nil { + fmt.Printf("Warning: ngrok failed to start: %v\n", err) + } else { + fmt.Printf("Ngrok tunnel starting...\n") + } + } else { + fmt.Printf("Warning: ngrok enabled but no auth token provided\n") + } + } + + // Check if TLS is enabled + if tlsEnabled { + // Convert TLS port to int + tlsPortInt, err := strconv.Atoi(tlsPort) + if err != nil { + return fmt.Errorf("invalid TLS port: %w", err) + } + + // Create TLS configuration + tlsConfig := &api.TLSConfig{ + Enabled: true, + Port: tlsPortInt, + Domain: tlsDomain, + SelfSigned: tlsSelfSigned, + CertPath: tlsCertPath, + KeyPath: tlsKeyPath, + AutoRedirect: tlsAutoRedirect, + } + + // Create TLS server + tlsServer := api.NewTLSServer(server, tlsConfig) + + // Print startup information for TLS + fmt.Printf("Starting VibeTunnel HTTPS server on %s:%s\n", bindAddress, tlsPort) + if tlsAutoRedirect { + fmt.Printf("HTTP redirect server on %s:%s -> HTTPS\n", bindAddress, port) + } + fmt.Printf("Serving web UI from: %s\n", staticPath) + fmt.Printf("Control directory: %s\n", controlPath) + + if tlsSelfSigned { + fmt.Printf("TLS: Using self-signed certificates for localhost\n") + } else if tlsDomain != "" { + fmt.Printf("TLS: Using Let's Encrypt for domain: %s\n", tlsDomain) + } else if tlsCertPath != "" && tlsKeyPath != "" { + fmt.Printf("TLS: Using custom certificates\n") + } + + if serverPassword != "" { + fmt.Printf("Basic auth enabled with username: admin\n") + } + + if ngrokURL != "" { + fmt.Printf("ngrok tunnel: %s\n", ngrokURL) + } + + if cfg.Advanced.DebugMode || debugMode { + fmt.Printf("Debug mode enabled\n") + } + + // Start TLS server + httpAddr := "" + if tlsAutoRedirect { + httpAddr = fmt.Sprintf("%s:%s", bindAddress, port) + } + httpsAddr := fmt.Sprintf("%s:%s", bindAddress, tlsPort) + + return tlsServer.StartTLS(httpAddr, httpsAddr) + } + + // Default HTTP behavior (like Rust version) + fmt.Printf("Starting VibeTunnel server on %s:%s\n", bindAddress, port) + fmt.Printf("Serving web UI from: %s\n", staticPath) + fmt.Printf("Control directory: %s\n", controlPath) + + if serverPassword != "" { + fmt.Printf("Basic auth enabled with username: admin\n") + } + + if ngrokURL != "" { + fmt.Printf("ngrok tunnel: %s\n", ngrokURL) + } + + if cfg.Advanced.DebugMode || debugMode { + fmt.Printf("Debug mode enabled\n") + } + + return server.Start(fmt.Sprintf("%s:%s", bindAddress, port)) +} + +func determineBind(cfg *config.Config) string { + // CLI flags take precedence + if localhost { + return "127.0.0.1" + } + if network { + return "0.0.0.0" + } + + // Check configuration + switch cfg.Server.AccessMode { + case "localhost": + return "127.0.0.1" + case "network": + return "0.0.0.0" + default: + // Default to localhost for security + return "127.0.0.1" + } +} + + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} \ No newline at end of file diff --git a/linux/debug_pty.go b/linux/debug_pty.go new file mode 100644 index 00000000..045efb99 --- /dev/null +++ b/linux/debug_pty.go @@ -0,0 +1,154 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "github.com/creack/pty" +) + +func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) + + // Test different shell configurations + tests := []struct { + name string + cmd []string + workDir string + }{ + {"zsh", []string{"zsh"}, "/Users/hjanuschka/agent-1"}, + {"zsh-interactive", []string{"zsh", "-i"}, "/Users/hjanuschka/agent-1"}, + {"bash", []string{"/bin/bash"}, "/Users/hjanuschka/agent-1"}, + {"bash-interactive", []string{"/bin/bash", "-i"}, "/Users/hjanuschka/agent-1"}, + {"sh", []string{"/bin/sh"}, "/Users/hjanuschka/agent-1"}, + {"sh-interactive", []string{"/bin/sh", "-i"}, "/Users/hjanuschka/agent-1"}, + } + + for _, test := range tests { + fmt.Printf("\n=== Testing: %s ===\n", test.name) + testShellSpawn(test.cmd, test.workDir) + time.Sleep(1 * time.Second) + } +} + +func testShellSpawn(cmdline []string, workDir string) { + log.Printf("Starting command: %v in directory: %s", cmdline, workDir) + + // Check if working directory exists + if _, err := os.Stat(workDir); err != nil { + log.Printf("Working directory %s not accessible: %v", workDir, err) + return + } + + // Create command + cmd := exec.Command(cmdline[0], cmdline[1:]...) + cmd.Dir = workDir + + // Set up environment + env := os.Environ() + env = append(env, "TERM=xterm-256color") + env = append(env, "SHELL="+cmdline[0]) + cmd.Env = env + + log.Printf("Command setup: %s, Args: %v, Dir: %s", cmd.Path, cmd.Args, cmd.Dir) + + // Start PTY + ptmx, err := pty.Start(cmd) + if err != nil { + log.Printf("Failed to start PTY: %v", err) + return + } + defer func() { + ptmx.Close() + if cmd.Process != nil { + cmd.Process.Kill() + } + }() + + log.Printf("PTY started successfully, PID: %d", cmd.Process.Pid) + + // Set PTY size + if err := pty.Setsize(ptmx, &pty.Winsize{Rows: 24, Cols: 80}); err != nil { + log.Printf("Failed to set PTY size: %v", err) + } + + // Monitor process for a few seconds + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + // Read initial output for 3 seconds + outputDone := make(chan bool) + go func() { + defer func() { outputDone <- true }() + buf := make([]byte, 1024) + timeout := time.After(3 * time.Second) + + for { + select { + case <-timeout: + log.Printf("Output reading timeout") + return + default: + ptmx.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + n, err := ptmx.Read(buf) + if n > 0 { + output := strings.TrimSpace(string(buf[:n])) + if output != "" { + log.Printf("PTY output: %q", output) + } + } + if err != nil && err != os.ErrDeadlineExceeded { + if err != io.EOF { + log.Printf("PTY read error: %v", err) + } + return + } + } + } + }() + + // Send a simple command to test interactivity + time.Sleep(500 * time.Millisecond) + log.Printf("Sending test command: 'echo hello'") + ptmx.Write([]byte("echo hello\n")) + + // Wait for either process exit or timeout + select { + case err := <-done: + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + log.Printf("Process exited with code: %d", status.ExitStatus()) + } else { + log.Printf("Process exited with error: %v", err) + } + } else { + log.Printf("Process exited with error: %v", err) + } + } else { + log.Printf("Process exited normally (code 0)") + } + case <-time.After(5 * time.Second): + log.Printf("Process still running after 5 seconds - looks good!") + if cmd.Process != nil { + cmd.Process.Signal(syscall.SIGTERM) + select { + case <-done: + log.Printf("Process terminated") + case <-time.After(2 * time.Second): + log.Printf("Process didn't respond to SIGTERM, killing") + cmd.Process.Kill() + } + } + } + + <-outputDone +} \ No newline at end of file diff --git a/linux/go.mod b/linux/go.mod new file mode 100644 index 00000000..19daf9fe --- /dev/null +++ b/linux/go.mod @@ -0,0 +1,65 @@ +module github.com/vibetunnel/linux + +go 1.24 + +toolchain go1.24.4 + +require ( + github.com/caddyserver/caddy/v2 v2.10.0 + github.com/caddyserver/certmagic v0.23.0 + github.com/creack/pty v1.1.21 + github.com/fsnotify/fsnotify v1.9.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/mux v1.8.1 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 + golang.ngrok.com/ngrok v1.13.0 + golang.org/x/term v0.30.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/caddy/v2 v2.10.0 // indirect + github.com/caddyserver/certmagic v0.23.0 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect + github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible // indirect + github.com/inconshreveable/log15/v3 v3.0.0-testing.5 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/libdns/libdns v1.0.0-beta.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mholt/acmez/v3 v3.1.2 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.50.1 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + golang.ngrok.com/muxado/v2 v2.0.1 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/linux/go.sum b/linux/go.sum new file mode 100644 index 00000000..a2c87bb1 --- /dev/null +++ b/linux/go.sum @@ -0,0 +1,301 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/caddyserver/caddy/v2 v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U= +github.com/caddyserver/caddy/v2 v2.10.0/go.mod h1:q+dgBS3xtIJJGYI2H5Nyh9+4BvhQQ9yCGmECv4Ubdjo= +github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= +github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible h1:VryeOTiaZfAzwx8xBcID1KlJCeoWSIpsNbSk+/D2LNk= +github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/log15/v3 v3.0.0-testing.5 h1:h4e0f3kjgg+RJBlKOabrohjHe47D3bbAB9BgMrc3DYA= +github.com/inconshreveable/log15/v3 v3.0.0-testing.5/go.mod h1:3GQg1SVrLoWGfRv/kAZMsdyU5cp8eFc1P3cw+Wwku94= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= +github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= +github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= +github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.ngrok.com/muxado/v2 v2.0.1 h1:jM9i6Pom6GGmnPrHKNR6OJRrUoHFkSZlJ3/S0zqdVpY= +golang.ngrok.com/muxado/v2 v2.0.1/go.mod h1:wzxJYX4xiAtmwumzL+QsukVwFRXmPNv86vB8RPpOxyM= +golang.ngrok.com/ngrok v1.13.0 h1:6SeOS+DAeIaHlkDmNH5waFHv0xjlavOV3wml0Z59/8k= +golang.ngrok.com/ngrok v1.13.0/go.mod h1:BKOMdoZXfD4w6o3EtE7Cu9TVbaUWBqptrZRWnVcAuI4= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/linux/pkg/api/fs.go b/linux/pkg/api/fs.go new file mode 100644 index 00000000..5fb589c3 --- /dev/null +++ b/linux/pkg/api/fs.go @@ -0,0 +1,49 @@ +package api + +import ( + "os" + "path/filepath" + "time" +) + +type FSEntry struct { + Name string `json:"name"` + Path string `json:"path"` + IsDir bool `json:"is_dir"` + Size int64 `json:"size"` + Mode string `json:"mode"` + ModTime time.Time `json:"mod_time"` +} + +func BrowseDirectory(path string) ([]FSEntry, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + entries, err := os.ReadDir(absPath) + if err != nil { + return nil, err + } + + var result []FSEntry + for _, entry := range entries { + info, err := entry.Info() + if err != nil { + continue + } + + fsEntry := FSEntry{ + Name: entry.Name(), + Path: filepath.Join(absPath, entry.Name()), + IsDir: entry.IsDir(), + Size: info.Size(), + Mode: info.Mode().String(), + ModTime: info.ModTime(), + } + + result = append(result, fsEntry) + } + + return result, nil +} \ No newline at end of file diff --git a/linux/pkg/api/multistream.go b/linux/pkg/api/multistream.go new file mode 100644 index 00000000..080fbfea --- /dev/null +++ b/linux/pkg/api/multistream.go @@ -0,0 +1,131 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "sync" + "time" + + "github.com/vibetunnel/linux/pkg/protocol" + "github.com/vibetunnel/linux/pkg/session" +) + +type MultiSSEStreamer struct { + w http.ResponseWriter + manager *session.Manager + sessionIDs []string + flusher http.Flusher + done chan struct{} + wg sync.WaitGroup +} + +func NewMultiSSEStreamer(w http.ResponseWriter, manager *session.Manager, sessionIDs []string) *MultiSSEStreamer { + flusher, _ := w.(http.Flusher) + return &MultiSSEStreamer{ + w: w, + manager: manager, + sessionIDs: sessionIDs, + flusher: flusher, + done: make(chan struct{}), + } +} + +func (m *MultiSSEStreamer) Stream() { + m.w.Header().Set("Content-Type", "text/event-stream") + m.w.Header().Set("Cache-Control", "no-cache") + m.w.Header().Set("Connection", "keep-alive") + m.w.Header().Set("X-Accel-Buffering", "no") + + // Start a goroutine for each session + for _, sessionID := range m.sessionIDs { + m.wg.Add(1) + go m.streamSession(sessionID) + } + + // Wait for all streams to complete + m.wg.Wait() +} + +func (m *MultiSSEStreamer) streamSession(sessionID string) { + defer m.wg.Done() + + sess, err := m.manager.GetSession(sessionID) + if err != nil { + m.sendError(sessionID, fmt.Sprintf("Session not found: %v", err)) + return + } + + streamPath := sess.StreamOutPath() + file, err := os.Open(streamPath) + if err != nil { + m.sendError(sessionID, fmt.Sprintf("Failed to open stream: %v", err)) + return + } + defer file.Close() + + // Seek to end for live streaming + file.Seek(0, io.SeekEnd) + + reader := protocol.NewStreamReader(file) + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-m.done: + return + case <-ticker.C: + for { + event, err := reader.Next() + if err != nil { + if err != io.EOF { + m.sendError(sessionID, fmt.Sprintf("Stream read error: %v", err)) + return + } + break + } + + if err := m.sendEvent(sessionID, event); err != nil { + return + } + + if event.Type == "end" { + return + } + } + } + } +} + +func (m *MultiSSEStreamer) sendEvent(sessionID string, event *protocol.StreamEvent) error { + data := map[string]interface{}{ + "session_id": sessionID, + "event": event, + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + if _, err := fmt.Fprintf(m.w, "data: %s\n\n", jsonData); err != nil { + return err // Client disconnected + } + + if m.flusher != nil { + m.flusher.Flush() + } + + return nil +} + +func (m *MultiSSEStreamer) sendError(sessionID string, message string) error { + event := &protocol.StreamEvent{ + Type: "error", + Message: message, + } + return m.sendEvent(sessionID, event) +} \ No newline at end of file diff --git a/linux/pkg/api/server.go b/linux/pkg/api/server.go new file mode 100644 index 00000000..087f1011 --- /dev/null +++ b/linux/pkg/api/server.go @@ -0,0 +1,500 @@ +package api + +import ( + "encoding/base64" + "encoding/json" + "log" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/gorilla/mux" + "github.com/vibetunnel/linux/pkg/ngrok" + "github.com/vibetunnel/linux/pkg/session" +) + +type Server struct { + manager *session.Manager + staticPath string + password string + ngrokService *ngrok.Service + port int +} + +func NewServer(manager *session.Manager, staticPath, password string, port int) *Server { + return &Server{ + manager: manager, + staticPath: staticPath, + password: password, + ngrokService: ngrok.NewService(), + port: port, + } +} + +func (s *Server) Start(addr string) error { + handler := s.createHandler() + return http.ListenAndServe(addr, handler) +} + +func (s *Server) createHandler() http.Handler { + r := mux.NewRouter() + + api := r.PathPrefix("/api").Subrouter() + if s.password != "" { + api.Use(s.basicAuthMiddleware) + } + + api.HandleFunc("/health", s.handleHealth).Methods("GET") + api.HandleFunc("/sessions", s.handleListSessions).Methods("GET") + api.HandleFunc("/sessions", s.handleCreateSession).Methods("POST") + api.HandleFunc("/sessions/{id}", s.handleGetSession).Methods("GET") + api.HandleFunc("/sessions/{id}/stream", s.handleStreamSession).Methods("GET") + api.HandleFunc("/sessions/{id}/snapshot", s.handleSnapshotSession).Methods("GET") + api.HandleFunc("/sessions/{id}/input", s.handleSendInput).Methods("POST") + api.HandleFunc("/sessions/{id}", s.handleKillSession).Methods("DELETE") + api.HandleFunc("/sessions/{id}/cleanup", s.handleCleanupSession).Methods("DELETE") + api.HandleFunc("/sessions/{id}/resize", s.handleResizeSession).Methods("POST") + api.HandleFunc("/sessions/multistream", s.handleMultistream).Methods("GET") + api.HandleFunc("/cleanup-exited", s.handleCleanupExited).Methods("POST") + api.HandleFunc("/fs/browse", s.handleBrowseFS).Methods("GET") + api.HandleFunc("/mkdir", s.handleMkdir).Methods("POST") + + // Ngrok endpoints + api.HandleFunc("/ngrok/start", s.handleNgrokStart).Methods("POST") + api.HandleFunc("/ngrok/stop", s.handleNgrokStop).Methods("POST") + api.HandleFunc("/ngrok/status", s.handleNgrokStatus).Methods("GET") + + if s.staticPath != "" { + r.PathPrefix("/").Handler(http.FileServer(http.Dir(s.staticPath))) + } + + return r +} + +func (s *Server) basicAuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth == "" { + s.unauthorized(w) + return + } + + const prefix = "Basic " + if !strings.HasPrefix(auth, prefix) { + s.unauthorized(w) + return + } + + decoded, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + s.unauthorized(w) + return + } + + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 || parts[0] != "admin" || parts[1] != s.password { + s.unauthorized(w) + return + } + + next.ServeHTTP(w, r) + }) +} + +func (s *Server) unauthorized(w http.ResponseWriter) { + w.Header().Set("WWW-Authenticate", `Basic realm="VibeTunnel"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) +} + +func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) +} + +func (s *Server) handleListSessions(w http.ResponseWriter, r *http.Request) { + sessions, err := s.manager.ListSessions() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(sessions) +} + +func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { + var req struct { + Name string `json:"name"` + Command []string `json:"command"` // Rust API format + WorkingDir string `json:"workingDir"` // Rust API format + Width int `json:"width"` // Terminal width + Height int `json:"height"` // Terminal height + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body. Expected JSON with 'command' array and optional 'workingDir'", http.StatusBadRequest) + return + } + + if len(req.Command) == 0 { + http.Error(w, "Command array is required", http.StatusBadRequest) + return + } + + cmdline := req.Command + cwd := req.WorkingDir + + // Set default terminal dimensions if not provided + width := req.Width + if width <= 0 { + width = 120 // Better default for modern terminals + } + height := req.Height + if height <= 0 { + height = 30 // Better default for modern terminals + } + + // Expand ~ in working directory + if cwd != "" && cwd[0] == '~' { + if cwd == "~" || cwd[:2] == "~/" { + homeDir, err := os.UserHomeDir() + if err == nil { + if cwd == "~" { + cwd = homeDir + } else { + cwd = filepath.Join(homeDir, cwd[2:]) + } + } + } + } + + sess, err := s.manager.CreateSession(session.Config{ + Name: req.Name, + Cmdline: cmdline, + Cwd: cwd, + Width: width, + Height: height, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Session created successfully", + "error": nil, + "sessionId": sess.ID, + }) +} + +func (s *Server) handleGetSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + // Return current session info without blocking on status update + // Status will be eventually consistent through background updates + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(sess) +} + +func (s *Server) handleStreamSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + streamer := NewSSEStreamer(w, sess) + streamer.Stream() +} + +func (s *Server) handleSnapshotSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + snapshot, err := GetSessionSnapshot(sess) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(snapshot) +} + +func (s *Server) handleSendInput(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + log.Printf("[ERROR] handleSendInput: Session %s not found", vars["id"]) + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + var req struct { + Input string `json:"input"` + Text string `json:"text"` // Alternative field name + Type string `json:"type"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + log.Printf("[ERROR] handleSendInput: Failed to decode request: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Handle alternative field names for compatibility + input := req.Input + if input == "" && req.Text != "" { + input = req.Text + } + + // Define special keys exactly as in Swift/macOS version + specialKeys := map[string]string{ + "arrow_up": "\x1b[A", + "arrow_down": "\x1b[B", + "arrow_right": "\x1b[C", + "arrow_left": "\x1b[D", + "escape": "\x1b", + "enter": "\r", // CR, not LF (to match Swift) + "ctrl_enter": "\r", // CR for ctrl+enter + "shift_enter": "\x1b\x0d", // ESC + CR for shift+enter + } + + // Check if this is a special key (automatic detection like Swift version) + if mappedKey, isSpecialKey := specialKeys[input]; isSpecialKey { + log.Printf("[DEBUG] handleSendInput: Sending special key '%s' (%q) to session %s", input, mappedKey, sess.ID[:8]) + err = sess.SendKey(mappedKey) + } else { + log.Printf("[DEBUG] handleSendInput: Sending text '%s' to session %s", input, sess.ID[:8]) + err = sess.SendText(input) + } + + if err != nil { + log.Printf("[ERROR] handleSendInput: Failed to send input: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + log.Printf("[DEBUG] handleSendInput: Successfully sent input to session %s", sess.ID[:8]) + w.WriteHeader(http.StatusNoContent) +} + +func (s *Server) handleKillSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + if err := sess.Kill(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Session deleted successfully", + }) +} + +func (s *Server) handleCleanupSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + if err := s.manager.RemoveSession(vars["id"]); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (s *Server) handleCleanupExited(w http.ResponseWriter, r *http.Request) { + if err := s.manager.CleanupExitedSessions(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (s *Server) handleMultistream(w http.ResponseWriter, r *http.Request) { + sessionIDs := r.URL.Query()["session_id"] + if len(sessionIDs) == 0 { + http.Error(w, "No session IDs provided", http.StatusBadRequest) + return + } + + streamer := NewMultiSSEStreamer(w, s.manager, sessionIDs) + streamer.Stream() +} + +func (s *Server) handleBrowseFS(w http.ResponseWriter, r *http.Request) { + path := r.URL.Query().Get("path") + if path == "" { + path = "." + } + + entries, err := BrowseDirectory(path) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(entries) +} + +func (s *Server) handleMkdir(w http.ResponseWriter, r *http.Request) { + var req struct { + Path string `json:"path"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := os.MkdirAll(req.Path, 0755); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (s *Server) handleResizeSession(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sess, err := s.manager.GetSession(vars["id"]) + if err != nil { + http.Error(w, "Session not found", http.StatusNotFound) + return + } + + var req struct { + Width int `json:"width"` + Height int `json:"height"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.Width <= 0 || req.Height <= 0 { + http.Error(w, "Width and height must be positive integers", http.StatusBadRequest) + return + } + + if err := sess.Resize(req.Width, req.Height); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Session resized successfully", + "width": req.Width, + "height": req.Height, + }) +} + +// Ngrok Handlers + +func (s *Server) handleNgrokStart(w http.ResponseWriter, r *http.Request) { + var req ngrok.StartRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.AuthToken == "" { + http.Error(w, "Auth token is required", http.StatusBadRequest) + return + } + + // Check if ngrok is already running + if s.ngrokService.IsRunning() { + status := s.ngrokService.GetStatus() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Ngrok tunnel is already running", + "tunnel": status, + }) + return + } + + // Start the tunnel + if err := s.ngrokService.Start(req.AuthToken, s.port); err != nil { + log.Printf("[ERROR] Failed to start ngrok tunnel: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Return immediate response - tunnel status will be updated asynchronously + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Ngrok tunnel is starting", + "tunnel": s.ngrokService.GetStatus(), + }) +} + +func (s *Server) handleNgrokStop(w http.ResponseWriter, r *http.Request) { + if !s.ngrokService.IsRunning() { + http.Error(w, "Ngrok tunnel is not running", http.StatusBadRequest) + return + } + + if err := s.ngrokService.Stop(); err != nil { + log.Printf("[ERROR] Failed to stop ngrok tunnel: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Ngrok tunnel stopped", + }) +} + +func (s *Server) handleNgrokStatus(w http.ResponseWriter, r *http.Request) { + status := s.ngrokService.GetStatus() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "tunnel": status, + }) +} + +// StartNgrok is a convenience method for CLI integration +func (s *Server) StartNgrok(authToken string) error { + return s.ngrokService.Start(authToken, s.port) +} + +// StopNgrok is a convenience method for CLI integration +func (s *Server) StopNgrok() error { + return s.ngrokService.Stop() +} + +// GetNgrokStatus returns the current ngrok status +func (s *Server) GetNgrokStatus() ngrok.StatusResponse { + return s.ngrokService.GetStatus() +} \ No newline at end of file diff --git a/linux/pkg/api/sse.go b/linux/pkg/api/sse.go new file mode 100644 index 00000000..c6db458b --- /dev/null +++ b/linux/pkg/api/sse.go @@ -0,0 +1,350 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/vibetunnel/linux/pkg/protocol" + "github.com/vibetunnel/linux/pkg/session" +) + +type SSEStreamer struct { + w http.ResponseWriter + session *session.Session + flusher http.Flusher +} + +func NewSSEStreamer(w http.ResponseWriter, session *session.Session) *SSEStreamer { + flusher, _ := w.(http.Flusher) + return &SSEStreamer{ + w: w, + session: session, + flusher: flusher, + } +} + +func (s *SSEStreamer) Stream() { + s.w.Header().Set("Content-Type", "text/event-stream") + s.w.Header().Set("Cache-Control", "no-cache") + s.w.Header().Set("Connection", "keep-alive") + s.w.Header().Set("X-Accel-Buffering", "no") + + streamPath := s.session.StreamOutPath() + + log.Printf("[DEBUG] SSE: Starting live stream for session %s", s.session.ID[:8]) + + // Create file watcher for high-performance event detection + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("[ERROR] SSE: Failed to create file watcher: %v", err) + s.sendError(fmt.Sprintf("Failed to create watcher: %v", err)) + return + } + defer watcher.Close() + + // Add the stream file to the watcher + err = watcher.Add(streamPath) + if err != nil { + log.Printf("[ERROR] SSE: Failed to watch stream file: %v", err) + s.sendError(fmt.Sprintf("Failed to watch file: %v", err)) + return + } + + headerSent := false + seenBytes := int64(0) + + // Send initial content immediately and check for client disconnect + if err := s.processNewContent(streamPath, &headerSent, &seenBytes); err != nil { + log.Printf("[DEBUG] SSE: Client disconnected during initial content: %v", err) + return + } + + // Watch for file changes + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + + // Process file writes (new content) and check for client disconnect + if event.Op&fsnotify.Write == fsnotify.Write { + if err := s.processNewContent(streamPath, &headerSent, &seenBytes); err != nil { + log.Printf("[DEBUG] SSE: Client disconnected during content streaming: %v", err) + return + } + } + + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("[ERROR] SSE: File watcher error: %v", err) + + case <-time.After(1 * time.Second): + // Check if session is still alive less frequently for better performance + if !s.session.IsAlive() { + log.Printf("[DEBUG] SSE: Session %s is dead, ending stream", s.session.ID[:8]) + if err := s.sendEvent(&protocol.StreamEvent{Type: "end"}); err != nil { + log.Printf("[DEBUG] SSE: Client disconnected during end event: %v", err) + } + return + } + } + } +} + +func (s *SSEStreamer) processNewContent(streamPath string, headerSent *bool, seenBytes *int64) error { + // Open the file for reading + file, err := os.Open(streamPath) + if err != nil { + log.Printf("[ERROR] SSE: Failed to open stream file: %v", err) + return err + } + defer file.Close() + + // Get current file size + fileInfo, err := file.Stat() + if err != nil { + log.Printf("[ERROR] SSE: Failed to stat stream file: %v", err) + return err + } + + currentSize := fileInfo.Size() + + // If file hasn't grown, nothing to do + if currentSize <= *seenBytes { + return nil + } + + // Seek to the position we last read + if _, err := file.Seek(*seenBytes, 0); err != nil { + log.Printf("[ERROR] SSE: Failed to seek to position %d: %v", *seenBytes, err) + return err + } + + // Read only the new content + newContentSize := currentSize - *seenBytes + newContent := make([]byte, newContentSize) + + bytesRead, err := file.Read(newContent) + if err != nil { + log.Printf("[ERROR] SSE: Failed to read new content: %v", err) + return err + } + + // Update seen bytes + *seenBytes = currentSize + + // Process the new content line by line + content := string(newContent[:bytesRead]) + lines := strings.Split(content, "\n") + + // Handle the case where the last line might be incomplete + // If the content doesn't end with a newline, don't process the last line yet + endIndex := len(lines) + if !strings.HasSuffix(content, "\n") && len(lines) > 0 { + // Move back the file position to exclude the incomplete line + incompleteLineBytes := int64(len(lines[len(lines)-1])) + *seenBytes -= incompleteLineBytes + endIndex = len(lines) - 1 + } + + // Process complete lines + for i := 0; i < endIndex; i++ { + line := lines[i] + if line == "" { + continue + } + + // Try to parse as header first + if !*headerSent { + var header protocol.AsciinemaHeader + if err := json.Unmarshal([]byte(line), &header); err == nil && header.Version > 0 { + *headerSent = true + log.Printf("[DEBUG] SSE: Sending event type=header") + // Skip sending header for now, frontend doesn't need it + continue + } + } + + // Try to parse as event array [timestamp, type, data] + var eventArray []interface{} + if err := json.Unmarshal([]byte(line), &eventArray); err == nil && len(eventArray) == 3 { + timestamp, ok1 := eventArray[0].(float64) + eventType, ok2 := eventArray[1].(string) + data, ok3 := eventArray[2].(string) + + if ok1 && ok2 && ok3 { + event := &protocol.StreamEvent{ + Type: "event", + Event: &protocol.AsciinemaEvent{ + Time: timestamp, + Type: protocol.EventType(eventType), + Data: data, + }, + } + + log.Printf("[DEBUG] SSE: Sending event type=%s", event.Type) + if err := s.sendRawEvent(event); err != nil { + log.Printf("[ERROR] SSE: Failed to send event: %v", err) + return err + } + } + } + } + return nil +} + +func (s *SSEStreamer) sendEvent(event *protocol.StreamEvent) error { + data, err := json.Marshal(event) + if err != nil { + return err + } + + lines := strings.Split(string(data), "\n") + for _, line := range lines { + if _, err := fmt.Fprintf(s.w, "data: %s\n", line); err != nil { + return err // Client disconnected + } + } + if _, err := fmt.Fprintf(s.w, "\n"); err != nil { + return err // Client disconnected + } + + if s.flusher != nil { + s.flusher.Flush() + } + + return nil +} + +func (s *SSEStreamer) sendRawEvent(event *protocol.StreamEvent) error { + var data interface{} + + if event.Type == "header" { + // For header events, we can skip them since the frontend might not expect them + // Or send them in a compatible format if needed + return nil + } else if event.Type == "event" && event.Event != nil { + // Convert to asciinema format: [timestamp, type, data] + data = []interface{}{ + event.Event.Time, + string(event.Event.Type), + event.Event.Data, + } + } else { + // For other event types, use the original format + data = event + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + lines := strings.Split(string(jsonData), "\n") + for _, line := range lines { + if _, err := fmt.Fprintf(s.w, "data: %s\n", line); err != nil { + return err // Client disconnected + } + } + if _, err := fmt.Fprintf(s.w, "\n"); err != nil { + return err // Client disconnected + } + + if s.flusher != nil { + s.flusher.Flush() + } + + return nil +} + +func (s *SSEStreamer) sendError(message string) error { + event := &protocol.StreamEvent{ + Type: "error", + Message: message, + } + return s.sendEvent(event) +} + +type SessionSnapshot struct { + SessionID string `json:"session_id"` + Header *protocol.AsciinemaHeader `json:"header"` + Events []protocol.AsciinemaEvent `json:"events"` +} + +func GetSessionSnapshot(sess *session.Session) (*SessionSnapshot, error) { + streamPath := sess.StreamOutPath() + file, err := os.Open(streamPath) + if err != nil { + return nil, err + } + defer file.Close() + + reader := protocol.NewStreamReader(file) + snapshot := &SessionSnapshot{ + SessionID: sess.ID, + Events: make([]protocol.AsciinemaEvent, 0), + } + + lastClearIndex := -1 + eventIndex := 0 + + for { + event, err := reader.Next() + if err != nil { + if err != io.EOF { + return nil, err + } + break + } + + switch event.Type { + case "header": + snapshot.Header = event.Header + case "event": + snapshot.Events = append(snapshot.Events, *event.Event) + if event.Event.Type == protocol.EventOutput && containsClearScreen(event.Event.Data) { + lastClearIndex = eventIndex + } + eventIndex++ + } + } + + if lastClearIndex >= 0 && lastClearIndex < len(snapshot.Events)-1 { + snapshot.Events = snapshot.Events[lastClearIndex:] + if len(snapshot.Events) > 0 { + firstTime := snapshot.Events[0].Time + for i := range snapshot.Events { + snapshot.Events[i].Time -= firstTime + } + } + } + + return snapshot, nil +} + +func containsClearScreen(data string) bool { + clearSequences := []string{ + "\x1b[H\x1b[2J", + "\x1b[2J", + "\x1b[3J", + "\x1bc", + } + + for _, seq := range clearSequences { + if strings.Contains(data, seq) { + return true + } + } + + return false +} \ No newline at end of file diff --git a/linux/pkg/api/tls_server.go b/linux/pkg/api/tls_server.go new file mode 100644 index 00000000..ce5bd3bf --- /dev/null +++ b/linux/pkg/api/tls_server.go @@ -0,0 +1,256 @@ +package api + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" + "net" + "net/http" + "path/filepath" + "time" + + "github.com/caddyserver/certmagic" +) + +// TLSConfig represents TLS configuration options +type TLSConfig struct { + Enabled bool `json:"enabled"` + Port int `json:"port"` + Domain string `json:"domain,omitempty"` // Optional domain for Let's Encrypt + SelfSigned bool `json:"self_signed"` // Use self-signed certificates + CertPath string `json:"cert_path,omitempty"` // Custom cert path + KeyPath string `json:"key_path,omitempty"` // Custom key path + AutoRedirect bool `json:"auto_redirect"` // Redirect HTTP to HTTPS +} + +// TLSServer wraps the regular server with TLS capabilities +type TLSServer struct { + *Server + tlsConfig *TLSConfig + certMagic *certmagic.Config +} + +// NewTLSServer creates a new TLS-enabled server +func NewTLSServer(server *Server, tlsConfig *TLSConfig) *TLSServer { + return &TLSServer{ + Server: server, + tlsConfig: tlsConfig, + } +} + +// StartTLS starts the server with TLS support +func (s *TLSServer) StartTLS(httpAddr, httpsAddr string) error { + if !s.tlsConfig.Enabled { + // Fall back to regular HTTP + return s.Start(httpAddr) + } + + // Set up TLS configuration + tlsConfig, err := s.setupTLS() + if err != nil { + return fmt.Errorf("failed to setup TLS: %w", err) + } + + // Create HTTP handler + handler := s.setupRoutes() + + // Start HTTPS server + httpsServer := &http.Server{ + Addr: httpsAddr, + Handler: handler, + TLSConfig: tlsConfig, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + log.Printf("Starting HTTPS server on %s", httpsAddr) + + // Start HTTP redirect server if enabled + if s.tlsConfig.AutoRedirect && httpAddr != "" { + go s.startHTTPRedirect(httpAddr, httpsAddr) + } + + // Start HTTPS server + if s.tlsConfig.SelfSigned || (s.tlsConfig.CertPath != "" && s.tlsConfig.KeyPath != "") { + return httpsServer.ListenAndServeTLS(s.tlsConfig.CertPath, s.tlsConfig.KeyPath) + } else { + // Use CertMagic for automatic certificates + return httpsServer.ListenAndServeTLS("", "") + } +} + +// setupTLS configures TLS based on the provided configuration +func (s *TLSServer) setupTLS() (*tls.Config, error) { + if s.tlsConfig.SelfSigned { + return s.setupSelfSignedTLS() + } + + if s.tlsConfig.CertPath != "" && s.tlsConfig.KeyPath != "" { + return s.setupCustomCertTLS() + } + + if s.tlsConfig.Domain != "" { + return s.setupCertMagicTLS() + } + + // Default to self-signed + return s.setupSelfSignedTLS() +} + +// setupSelfSignedTLS creates a self-signed certificate +func (s *TLSServer) setupSelfSignedTLS() (*tls.Config, error) { + // Generate self-signed certificate + cert, err := s.generateSelfSignedCert() + if err != nil { + return nil, fmt.Errorf("failed to generate self-signed certificate: %w", err) + } + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + ServerName: "localhost", + MinVersion: tls.VersionTLS12, + }, nil +} + +// setupCustomCertTLS loads custom certificates +func (s *TLSServer) setupCustomCertTLS() (*tls.Config, error) { + cert, err := tls.LoadX509KeyPair(s.tlsConfig.CertPath, s.tlsConfig.KeyPath) + if err != nil { + return nil, fmt.Errorf("failed to load custom certificates: %w", err) + } + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + }, nil +} + +// setupCertMagicTLS configures automatic certificate management +func (s *TLSServer) setupCertMagicTLS() (*tls.Config, error) { + // Set up CertMagic for automatic HTTPS + certmagic.DefaultACME.Agreed = true + certmagic.DefaultACME.Email = "admin@" + s.tlsConfig.Domain + + // Configure storage path + certmagic.Default.Storage = &certmagic.FileStorage{ + Path: filepath.Join("/tmp", "vibetunnel-certs"), + } + + // Get certificate for domain + err := certmagic.ManageSync(context.Background(), []string{s.tlsConfig.Domain}) + if err != nil { + return nil, fmt.Errorf("failed to obtain certificate for domain %s: %w", s.tlsConfig.Domain, err) + } + + tlsConfig, err := certmagic.TLS([]string{s.tlsConfig.Domain}) + if err != nil { + return nil, fmt.Errorf("failed to create TLS config: %w", err) + } + return tlsConfig, nil +} + +// generateSelfSignedCert creates a self-signed certificate for localhost +func (s *TLSServer) generateSelfSignedCert() (tls.Certificate, error) { + // Generate RSA private key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to generate private key: %w", err) + } + + // Create certificate template + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"VibeTunnel"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"localhost"}, + StreetAddress: []string{""}, + PostalCode: []string{""}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), // Valid for 1 year + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + DNSNames: []string{"localhost"}, + } + + // Generate certificate + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to create certificate: %w", err) + } + + // Encode certificate to PEM + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + // Encode private key to PEM + privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to marshal private key: %w", err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyDER}) + + // Create TLS certificate + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to create X509 key pair: %w", err) + } + + return cert, nil +} + +// startHTTPRedirect starts an HTTP server that redirects all requests to HTTPS +func (s *TLSServer) startHTTPRedirect(httpAddr, httpsAddr string) { + redirectHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Extract host from httpsAddr for redirect + host := r.Host + if host == "" { + host = "localhost" + } + + // Remove port if present and add HTTPS port + if colonIndex := len(host) - 1; host[colonIndex] == ':' { + // Remove existing port + for i := colonIndex - 1; i >= 0; i-- { + if host[i] == ':' { + host = host[:i] + break + } + } + } + + // Add HTTPS port + if s.tlsConfig.Port != 443 { + host = fmt.Sprintf("%s:%d", host, s.tlsConfig.Port) + } + + httpsURL := fmt.Sprintf("https://%s%s", host, r.RequestURI) + http.Redirect(w, r, httpsURL, http.StatusPermanentRedirect) + }) + + server := &http.Server{ + Addr: httpAddr, + Handler: redirectHandler, + } + + log.Printf("Starting HTTP redirect server on %s -> HTTPS", httpAddr) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("HTTP redirect server error: %v", err) + } +} + +// setupRoutes returns the configured HTTP handler (reusing existing Server logic) +func (s *TLSServer) setupRoutes() http.Handler { + // Use the existing server's router setup + return s.Server.createHandler() +} \ No newline at end of file diff --git a/linux/pkg/config/config.go b/linux/pkg/config/config.go new file mode 100644 index 00000000..ef6abf3c --- /dev/null +++ b/linux/pkg/config/config.go @@ -0,0 +1,240 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/pflag" + "gopkg.in/yaml.v3" +) + +// Config represents the VibeTunnel configuration +// Mirrors the structure of VibeTunnel's settings system +type Config struct { + ControlPath string `yaml:"control_path"` + Server Server `yaml:"server"` + Security Security `yaml:"security"` + Ngrok Ngrok `yaml:"ngrok"` + Advanced Advanced `yaml:"advanced"` + Update Update `yaml:"update"` +} + +// Server configuration (mirrors DashboardSettingsView.swift) +type Server struct { + Port string `yaml:"port"` + AccessMode string `yaml:"access_mode"` // "localhost" or "network" + StaticPath string `yaml:"static_path"` + Mode string `yaml:"mode"` // "native" or "rust" +} + +// Security configuration (mirrors dashboard password settings) +type Security struct { + PasswordEnabled bool `yaml:"password_enabled"` + Password string `yaml:"password"` +} + +// Ngrok configuration (mirrors NgrokService.swift) +type Ngrok struct { + Enabled bool `yaml:"enabled"` + AuthToken string `yaml:"auth_token"` + TokenStored bool `yaml:"token_stored"` +} + +// Advanced configuration (mirrors AdvancedSettingsView.swift) +type Advanced struct { + DebugMode bool `yaml:"debug_mode"` + CleanupStartup bool `yaml:"cleanup_startup"` + PreferredTerm string `yaml:"preferred_terminal"` +} + +// Update configuration (mirrors UpdateChannel.swift) +type Update struct { + Channel string `yaml:"channel"` // "stable" or "prerelease" + AutoCheck bool `yaml:"auto_check"` + ShowNotifications bool `yaml:"show_notifications"` +} + +// DefaultConfig returns a configuration with VibeTunnel-compatible defaults +func DefaultConfig() *Config { + homeDir, _ := os.UserHomeDir() + return &Config{ + ControlPath: filepath.Join(homeDir, ".vibetunnel", "control"), + Server: Server{ + Port: "4020", // Matches VibeTunnel default + AccessMode: "localhost", + Mode: "native", + }, + Security: Security{ + PasswordEnabled: false, + }, + Ngrok: Ngrok{ + Enabled: false, + }, + Advanced: Advanced{ + DebugMode: false, + CleanupStartup: false, + PreferredTerm: "auto", + }, + Update: Update{ + Channel: "stable", + AutoCheck: true, + ShowNotifications: true, + }, + } +} + +// LoadConfig loads configuration from file, creates default if not exists +func LoadConfig(filename string) *Config { + cfg := DefaultConfig() + + if filename == "" { + return cfg + } + + // Create config directory if it doesn't exist + if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + fmt.Printf("Warning: failed to create config directory: %v\n", err) + return cfg + } + + // Try to read existing config + data, err := os.ReadFile(filename) + if err != nil { + if !os.IsNotExist(err) { + fmt.Printf("Warning: failed to read config file: %v\n", err) + } + // Save default config + cfg.Save(filename) + return cfg + } + + // Parse existing config + if err := yaml.Unmarshal(data, cfg); err != nil { + fmt.Printf("Warning: failed to parse config file: %v\n", err) + return DefaultConfig() + } + + return cfg +} + +// Save saves the configuration to file +func (c *Config) Save(filename string) error { + data, err := yaml.Marshal(c) + if err != nil { + return err + } + + return os.WriteFile(filename, data, 0644) +} + +// MergeFlags merges command line flags into the configuration +func (c *Config) MergeFlags(flags *pflag.FlagSet) { + // Only merge flags that were actually set by the user + if flags.Changed("port") { + if val, err := flags.GetString("port"); err == nil { + c.Server.Port = val + } + } + + if flags.Changed("localhost") { + if val, err := flags.GetBool("localhost"); err == nil && val { + c.Server.AccessMode = "localhost" + } + } + + if flags.Changed("network") { + if val, err := flags.GetBool("network"); err == nil && val { + c.Server.AccessMode = "network" + } + } + + if flags.Changed("password") { + if val, err := flags.GetString("password"); err == nil && val != "" { + c.Security.Password = val + c.Security.PasswordEnabled = true + } + } + + if flags.Changed("password-enabled") { + if val, err := flags.GetBool("password-enabled"); err == nil { + c.Security.PasswordEnabled = val + } + } + + if flags.Changed("ngrok") { + if val, err := flags.GetBool("ngrok"); err == nil { + c.Ngrok.Enabled = val + } + } + + if flags.Changed("ngrok-token") { + if val, err := flags.GetString("ngrok-token"); err == nil && val != "" { + c.Ngrok.AuthToken = val + c.Ngrok.TokenStored = true + } + } + + if flags.Changed("debug") { + if val, err := flags.GetBool("debug"); err == nil { + c.Advanced.DebugMode = val + } + } + + if flags.Changed("cleanup-startup") { + if val, err := flags.GetBool("cleanup-startup"); err == nil { + c.Advanced.CleanupStartup = val + } + } + + if flags.Changed("server-mode") { + if val, err := flags.GetString("server-mode"); err == nil { + c.Server.Mode = val + } + } + + if flags.Changed("update-channel") { + if val, err := flags.GetString("update-channel"); err == nil { + c.Update.Channel = val + } + } + + if flags.Changed("static-path") { + if val, err := flags.GetString("static-path"); err == nil { + c.Server.StaticPath = val + } + } + + if flags.Changed("control-path") { + if val, err := flags.GetString("control-path"); err == nil { + c.ControlPath = val + } + } +} + +// Print displays the current configuration +func (c *Config) Print() { + fmt.Println("VibeTunnel Configuration:") + fmt.Printf(" Control Path: %s\n", c.ControlPath) + fmt.Println("\nServer:") + fmt.Printf(" Port: %s\n", c.Server.Port) + fmt.Printf(" Access Mode: %s\n", c.Server.AccessMode) + fmt.Printf(" Static Path: %s\n", c.Server.StaticPath) + fmt.Printf(" Mode: %s\n", c.Server.Mode) + fmt.Println("\nSecurity:") + fmt.Printf(" Password Enabled: %t\n", c.Security.PasswordEnabled) + if c.Security.PasswordEnabled { + fmt.Printf(" Password: [hidden]\n") + } + fmt.Println("\nNgrok:") + fmt.Printf(" Enabled: %t\n", c.Ngrok.Enabled) + fmt.Printf(" Token Stored: %t\n", c.Ngrok.TokenStored) + fmt.Println("\nAdvanced:") + fmt.Printf(" Debug Mode: %t\n", c.Advanced.DebugMode) + fmt.Printf(" Cleanup on Startup: %t\n", c.Advanced.CleanupStartup) + fmt.Printf(" Preferred Terminal: %s\n", c.Advanced.PreferredTerm) + fmt.Println("\nUpdate:") + fmt.Printf(" Channel: %s\n", c.Update.Channel) + fmt.Printf(" Auto Check: %t\n", c.Update.AutoCheck) + fmt.Printf(" Show Notifications: %t\n", c.Update.ShowNotifications) +} \ No newline at end of file diff --git a/linux/pkg/ngrok/service.go b/linux/pkg/ngrok/service.go new file mode 100644 index 00000000..2af67140 --- /dev/null +++ b/linux/pkg/ngrok/service.go @@ -0,0 +1,159 @@ +package ngrok + +import ( + "context" + "fmt" + "log" + "net/url" + "time" + + "golang.ngrok.com/ngrok" + "golang.ngrok.com/ngrok/config" +) + +// NewService creates a new ngrok service instance +func NewService() *Service { + ctx, cancel := context.WithCancel(context.Background()) + return &Service{ + ctx: ctx, + cancel: cancel, + info: TunnelInfo{ + Status: StatusDisconnected, + }, + } +} + +// Start initiates a new ngrok tunnel +func (s *Service) Start(authToken string, localPort int) error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.info.Status == StatusConnected || s.info.Status == StatusConnecting { + return ErrAlreadyRunning + } + + s.info.Status = StatusConnecting + s.info.Error = "" + s.info.LocalURL = fmt.Sprintf("http://127.0.0.1:%d", localPort) + + // Start tunnel in a goroutine + go func() { + if err := s.startTunnel(authToken, localPort); err != nil { + s.mu.Lock() + s.info.Status = StatusError + s.info.Error = err.Error() + s.mu.Unlock() + log.Printf("[ERROR] Ngrok tunnel failed: %v", err) + } + }() + + return nil +} + +// startTunnel creates and maintains the ngrok tunnel +func (s *Service) startTunnel(authToken string, localPort int) error { + // Create local URL for forwarding + localURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localPort)) + if err != nil { + return fmt.Errorf("invalid local port: %w", err) + } + + // Create forwarder that automatically handles the tunnel and forwarding + forwarder, err := ngrok.ListenAndForward(s.ctx, localURL, config.HTTPEndpoint(), ngrok.WithAuthtoken(authToken)) + if err != nil { + return fmt.Errorf("failed to create ngrok tunnel: %w", err) + } + + s.mu.Lock() + s.forwarder = forwarder + s.info.URL = forwarder.URL() + s.info.Status = StatusConnected + s.info.ConnectedAt = time.Now() + s.mu.Unlock() + + log.Printf("[INFO] Ngrok tunnel established: %s -> http://127.0.0.1:%d", forwarder.URL(), localPort) + + // Wait for the forwarder to close + return forwarder.Wait() +} + + + +// Stop terminates the ngrok tunnel +func (s *Service) Stop() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.info.Status == StatusDisconnected { + return ErrNotConnected + } + + // Cancel context to stop all operations + s.cancel() + + // Close forwarder if it exists + if s.forwarder != nil { + if err := s.forwarder.Close(); err != nil { + log.Printf("[WARNING] Error closing ngrok forwarder: %v", err) + } + s.forwarder = nil + } + + // Reset status + s.info.Status = StatusDisconnected + s.info.URL = "" + s.info.Error = "" + s.info.ConnectedAt = time.Time{} + + // Create new context for potential restart + s.ctx, s.cancel = context.WithCancel(context.Background()) + + log.Printf("[INFO] Ngrok tunnel stopped") + return nil +} + +// GetStatus returns the current tunnel status +func (s *Service) GetStatus() StatusResponse { + s.mu.RLock() + defer s.mu.RUnlock() + + return StatusResponse{ + TunnelInfo: s.info, + IsRunning: s.info.Status == StatusConnected || s.info.Status == StatusConnecting, + } +} + +// IsRunning returns true if the tunnel is active +func (s *Service) IsRunning() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.info.Status == StatusConnected || s.info.Status == StatusConnecting +} + +// GetURL returns the public tunnel URL +func (s *Service) GetURL() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.info.URL +} + +// SetConfig updates the ngrok configuration +func (s *Service) SetConfig(config Config) { + s.mu.Lock() + defer s.mu.Unlock() + s.config = config +} + +// GetConfig returns the current configuration +func (s *Service) GetConfig() Config { + s.mu.RLock() + defer s.mu.RUnlock() + return s.config +} + +// Cleanup performs cleanup when the service is being destroyed +func (s *Service) Cleanup() { + if err := s.Stop(); err != nil && err != ErrNotConnected { + log.Printf("[WARNING] Error during ngrok cleanup: %v", err) + } +} \ No newline at end of file diff --git a/linux/pkg/ngrok/types.go b/linux/pkg/ngrok/types.go new file mode 100644 index 00000000..52df583d --- /dev/null +++ b/linux/pkg/ngrok/types.go @@ -0,0 +1,77 @@ +package ngrok + +import ( + "context" + "golang.ngrok.com/ngrok" + "sync" + "time" +) + +// Status represents the current state of ngrok tunnel +type Status string + +const ( + StatusDisconnected Status = "disconnected" + StatusConnecting Status = "connecting" + StatusConnected Status = "connected" + StatusError Status = "error" +) + +// TunnelInfo contains information about the active tunnel +type TunnelInfo struct { + URL string `json:"url"` + Status Status `json:"status"` + ConnectedAt time.Time `json:"connected_at,omitempty"` + Error string `json:"error,omitempty"` + LocalURL string `json:"local_url"` + TunnelVersion string `json:"tunnel_version,omitempty"` +} + +// Config holds ngrok configuration +type Config struct { + AuthToken string `json:"auth_token"` + Enabled bool `json:"enabled"` +} + +// Service manages ngrok tunnel lifecycle +type Service struct { + mu sync.RWMutex + forwarder ngrok.Forwarder + info TunnelInfo + config Config + ctx context.Context + cancel context.CancelFunc +} + +// StartRequest represents the request to start ngrok tunnel +type StartRequest struct { + AuthToken string `json:"auth_token,omitempty"` +} + +// StatusResponse represents the response for tunnel status +type StatusResponse struct { + TunnelInfo + IsRunning bool `json:"is_running"` +} + +// NgrokError represents ngrok-specific errors +type NgrokError struct { + Code string `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +func (e NgrokError) Error() string { + if e.Details != "" { + return e.Message + ": " + e.Details + } + return e.Message +} + +// Common ngrok errors +var ( + ErrNotConnected = NgrokError{Code: "not_connected", Message: "Ngrok tunnel is not connected"} + ErrAlreadyRunning = NgrokError{Code: "already_running", Message: "Ngrok tunnel is already running"} + ErrInvalidAuthToken = NgrokError{Code: "invalid_auth_token", Message: "Invalid ngrok auth token"} + ErrTunnelFailed = NgrokError{Code: "tunnel_failed", Message: "Failed to establish tunnel"} +) \ No newline at end of file diff --git a/linux/pkg/protocol/asciinema.go b/linux/pkg/protocol/asciinema.go new file mode 100644 index 00000000..01c89465 --- /dev/null +++ b/linux/pkg/protocol/asciinema.go @@ -0,0 +1,326 @@ +package protocol + +import ( + "encoding/json" + "fmt" + "io" + "os" + "sync" + "time" +) + +type AsciinemaHeader struct { + Version uint32 `json:"version"` + Width uint32 `json:"width"` + Height uint32 `json:"height"` + Timestamp int64 `json:"timestamp,omitempty"` + Command string `json:"command,omitempty"` + Title string `json:"title,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +type EventType string + +const ( + EventOutput EventType = "o" + EventInput EventType = "i" + EventResize EventType = "r" + EventMarker EventType = "m" +) + +type AsciinemaEvent struct { + Time float64 `json:"time"` + Type EventType `json:"type"` + Data string `json:"data"` +} + +type StreamEvent struct { + Type string `json:"type"` + Header *AsciinemaHeader `json:"header,omitempty"` + Event *AsciinemaEvent `json:"event,omitempty"` + Message string `json:"message,omitempty"` +} + +type StreamWriter struct { + writer io.Writer + header *AsciinemaHeader + startTime time.Time + mutex sync.Mutex + closed bool + buffer []byte + lastWrite time.Time + flushTimer *time.Timer + syncTimer *time.Timer + needsSync bool +} + +func NewStreamWriter(writer io.Writer, header *AsciinemaHeader) *StreamWriter { + return &StreamWriter{ + writer: writer, + header: header, + startTime: time.Now(), + buffer: make([]byte, 0, 4096), + lastWrite: time.Now(), + } +} + +func (w *StreamWriter) WriteHeader() error { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.closed { + return fmt.Errorf("stream writer closed") + } + + if w.header.Timestamp == 0 { + w.header.Timestamp = w.startTime.Unix() + } + + data, err := json.Marshal(w.header) + if err != nil { + return err + } + + _, err = fmt.Fprintf(w.writer, "%s\n", data) + return err +} + +func (w *StreamWriter) WriteOutput(data []byte) error { + return w.writeEvent(EventOutput, data) +} + +func (w *StreamWriter) WriteInput(data []byte) error { + return w.writeEvent(EventInput, data) +} + +func (w *StreamWriter) WriteResize(width, height uint32) error { + data := fmt.Sprintf("%dx%d", width, height) + return w.writeEvent(EventResize, []byte(data)) +} + +func (w *StreamWriter) writeEvent(eventType EventType, data []byte) error { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.closed { + return fmt.Errorf("stream writer closed") + } + + w.buffer = append(w.buffer, data...) + w.lastWrite = time.Now() + + completeData, remaining := extractCompleteUTF8(w.buffer) + w.buffer = remaining + + if len(completeData) == 0 { + // If we have incomplete UTF-8 data, set up a timer to flush it after a short delay + if len(w.buffer) > 0 { + w.scheduleFlush() + } + return nil + } + + elapsed := time.Since(w.startTime).Seconds() + event := []interface{}{elapsed, string(eventType), string(completeData)} + + eventData, err := json.Marshal(event) + if err != nil { + return err + } + + _, err = fmt.Fprintf(w.writer, "%s\n", eventData) + if err != nil { + return err + } + + // Schedule sync instead of immediate sync for better performance + w.scheduleBatchSync() + + return nil +} + +// scheduleFlush sets up a timer to flush incomplete UTF-8 data after a short delay +func (w *StreamWriter) scheduleFlush() { + // Cancel existing timer if any + if w.flushTimer != nil { + w.flushTimer.Stop() + } + + // Set up new timer for 5ms flush delay + w.flushTimer = time.AfterFunc(5*time.Millisecond, func() { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.closed || len(w.buffer) == 0 { + return + } + + // Force flush incomplete UTF-8 data for real-time streaming + elapsed := time.Since(w.startTime).Seconds() + event := []interface{}{elapsed, string(EventOutput), string(w.buffer)} + + eventData, err := json.Marshal(event) + if err != nil { + return + } + + fmt.Fprintf(w.writer, "%s\n", eventData) + + // Schedule sync instead of immediate sync for better performance + w.scheduleBatchSync() + + // Clear buffer after flushing + w.buffer = w.buffer[:0] + }) +} + +// scheduleBatchSync batches sync operations to reduce I/O overhead +func (w *StreamWriter) scheduleBatchSync() { + w.needsSync = true + + // Cancel existing sync timer if any + if w.syncTimer != nil { + w.syncTimer.Stop() + } + + // Schedule sync after 5ms to batch multiple writes + w.syncTimer = time.AfterFunc(5*time.Millisecond, func() { + if w.needsSync { + if file, ok := w.writer.(*os.File); ok { + file.Sync() + } + w.needsSync = false + } + }) +} + +func (w *StreamWriter) Close() error { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.closed { + return nil + } + + // Cancel timers + if w.flushTimer != nil { + w.flushTimer.Stop() + } + if w.syncTimer != nil { + w.syncTimer.Stop() + } + + if len(w.buffer) > 0 { + elapsed := time.Since(w.startTime).Seconds() + event := []interface{}{elapsed, string(EventOutput), string(w.buffer)} + eventData, _ := json.Marshal(event) + fmt.Fprintf(w.writer, "%s\n", eventData) + } + + w.closed = true + if closer, ok := w.writer.(io.Closer); ok { + return closer.Close() + } + + return nil +} + +func extractCompleteUTF8(data []byte) (complete, remaining []byte) { + if len(data) == 0 { + return nil, nil + } + + lastValid := len(data) + for i := len(data) - 1; i >= 0 && i >= len(data)-4; i-- { + if data[i]&0x80 == 0 { + break + } + if data[i]&0xC0 == 0xC0 { + expectedLen := 1 + if data[i]&0xE0 == 0xC0 { + expectedLen = 2 + } else if data[i]&0xF0 == 0xE0 { + expectedLen = 3 + } else if data[i]&0xF8 == 0xF0 { + expectedLen = 4 + } + + if i+expectedLen > len(data) { + lastValid = i + } + break + } + } + + return data[:lastValid], data[lastValid:] +} + +type StreamReader struct { + reader io.Reader + decoder *json.Decoder + header *AsciinemaHeader + headerRead bool +} + +func NewStreamReader(reader io.Reader) *StreamReader { + return &StreamReader{ + reader: reader, + decoder: json.NewDecoder(reader), + } +} + +func (r *StreamReader) Next() (*StreamEvent, error) { + if !r.headerRead { + var header AsciinemaHeader + if err := r.decoder.Decode(&header); err != nil { + return nil, err + } + r.header = &header + r.headerRead = true + return &StreamEvent{ + Type: "header", + Header: &header, + }, nil + } + + var raw json.RawMessage + if err := r.decoder.Decode(&raw); err != nil { + if err == io.EOF { + return &StreamEvent{Type: "end"}, nil + } + return nil, err + } + + var array []interface{} + if err := json.Unmarshal(raw, &array); err != nil { + return nil, err + } + + if len(array) != 3 { + return nil, fmt.Errorf("invalid event format") + } + + timestamp, ok := array[0].(float64) + if !ok { + return nil, fmt.Errorf("invalid timestamp") + } + + eventType, ok := array[1].(string) + if !ok { + return nil, fmt.Errorf("invalid event type") + } + + data, ok := array[2].(string) + if !ok { + return nil, fmt.Errorf("invalid event data") + } + + return &StreamEvent{ + Type: "event", + Event: &AsciinemaEvent{ + Time: timestamp, + Type: EventType(eventType), + Data: data, + }, + }, nil +} \ No newline at end of file diff --git a/linux/pkg/session/manager.go b/linux/pkg/session/manager.go new file mode 100644 index 00000000..ce88086e --- /dev/null +++ b/linux/pkg/session/manager.go @@ -0,0 +1,140 @@ +package session + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" +) + +type Manager struct { + controlPath string + runningSessions map[string]*Session + mutex sync.RWMutex +} + +func NewManager(controlPath string) *Manager { + return &Manager{ + controlPath: controlPath, + runningSessions: make(map[string]*Session), + } +} + +func (m *Manager) CreateSession(config Config) (*Session, error) { + if err := os.MkdirAll(m.controlPath, 0755); err != nil { + return nil, fmt.Errorf("failed to create control directory: %w", err) + } + + session, err := newSession(m.controlPath, config) + if err != nil { + return nil, err + } + + if err := session.Start(); err != nil { + os.RemoveAll(session.Path()) + return nil, err + } + + // Add to running sessions registry + m.mutex.Lock() + m.runningSessions[session.ID] = session + m.mutex.Unlock() + + return session, nil +} + +func (m *Manager) GetSession(id string) (*Session, error) { + // First check if we have this session in our running sessions registry + m.mutex.RLock() + if session, exists := m.runningSessions[id]; exists { + m.mutex.RUnlock() + return session, nil + } + m.mutex.RUnlock() + + // Fall back to loading from disk (for sessions that might have been started before this manager instance) + return loadSession(m.controlPath, id) +} + +func (m *Manager) FindSession(nameOrID string) (*Session, error) { + sessions, err := m.ListSessions() + if err != nil { + return nil, err + } + + for _, s := range sessions { + if s.ID == nameOrID || s.Name == nameOrID || strings.HasPrefix(s.ID, nameOrID) { + return m.GetSession(s.ID) + } + } + + return nil, fmt.Errorf("session not found: %s", nameOrID) +} + +func (m *Manager) ListSessions() ([]*Info, error) { + entries, err := os.ReadDir(m.controlPath) + if err != nil { + if os.IsNotExist(err) { + return []*Info{}, nil + } + return nil, err + } + + sessions := make([]*Info, 0) + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + session, err := loadSession(m.controlPath, entry.Name()) + if err != nil { + continue + } + + // Return cached status for faster response - background updates will keep it current + sessions = append(sessions, session.info) + } + + sort.Slice(sessions, func(i, j int) bool { + return sessions[i].StartedAt.After(sessions[j].StartedAt) + }) + + return sessions, nil +} + +func (m *Manager) CleanupExitedSessions() error { + sessions, err := m.ListSessions() + if err != nil { + return err + } + + var errs []error + for _, info := range sessions { + if info.Status == string(StatusExited) { + sessionPath := filepath.Join(m.controlPath, info.ID) + if err := os.RemoveAll(sessionPath); err != nil { + errs = append(errs, fmt.Errorf("failed to remove %s: %w", info.ID, err)) + } else { + fmt.Printf("Cleaned up session: %s\n", info.ID) + } + } + } + + if len(errs) > 0 { + return fmt.Errorf("cleanup errors: %v", errs) + } + + return nil +} + +func (m *Manager) RemoveSession(id string) error { + // Remove from running sessions registry + m.mutex.Lock() + delete(m.runningSessions, id) + m.mutex.Unlock() + + sessionPath := filepath.Join(m.controlPath, id) + return os.RemoveAll(sessionPath) +} \ No newline at end of file diff --git a/linux/pkg/session/pty.go b/linux/pkg/session/pty.go new file mode 100644 index 00000000..6e10aadc --- /dev/null +++ b/linux/pkg/session/pty.go @@ -0,0 +1,366 @@ +package session + +import ( + "fmt" + "io" + "log" + "os" + "os/exec" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + "github.com/creack/pty" + "github.com/vibetunnel/linux/pkg/protocol" + "golang.org/x/term" +) + +type PTY struct { + session *Session + cmd *exec.Cmd + pty *os.File + oldState *term.State + streamWriter *protocol.StreamWriter + stdinPipe *os.File + resizeMutex sync.Mutex +} + +func NewPTY(session *Session) (*PTY, error) { + log.Printf("[DEBUG] NewPTY: Starting PTY creation for session %s", session.ID[:8]) + + shell := os.Getenv("SHELL") + if shell == "" { + shell = "/bin/bash" + } + + cmdline := session.info.Args + if len(cmdline) == 0 { + cmdline = []string{shell} + } + + log.Printf("[DEBUG] NewPTY: Initial cmdline: %v", cmdline) + + // For shells, force interactive mode to prevent immediate exit + if len(cmdline) == 1 && (strings.HasSuffix(cmdline[0], "bash") || strings.HasSuffix(cmdline[0], "zsh") || strings.HasSuffix(cmdline[0], "sh")) { + cmdline = append(cmdline, "-i") + // Update session info to reflect the actual command being run + session.info.Args = cmdline + session.info.Cmdline = strings.Join(cmdline, " ") + log.Printf("[DEBUG] NewPTY: Added -i flag, cmdline now: %v", cmdline) + } + + cmd := exec.Command(cmdline[0], cmdline[1:]...) + + // Set working directory, ensuring it's valid + if session.info.Cwd != "" { + // Verify the directory exists and is accessible + if _, err := os.Stat(session.info.Cwd); err != nil { + log.Printf("[ERROR] NewPTY: Working directory '%s' not accessible: %v", session.info.Cwd, err) + return nil, fmt.Errorf("working directory '%s' not accessible: %w", session.info.Cwd, err) + } + cmd.Dir = session.info.Cwd + log.Printf("[DEBUG] NewPTY: Set working directory to: %s", session.info.Cwd) + } + + // Set up environment with proper terminal settings + env := os.Environ() + env = append(env, "TERM="+session.info.Term) + env = append(env, "SHELL="+cmdline[0]) + cmd.Env = env + + ptmx, err := pty.Start(cmd) + if err != nil { + log.Printf("[ERROR] NewPTY: Failed to start PTY: %v", err) + return nil, fmt.Errorf("failed to start PTY: %w", err) + } + + log.Printf("[DEBUG] NewPTY: PTY started successfully, PID: %d", cmd.Process.Pid) + + if err := pty.Setsize(ptmx, &pty.Winsize{ + Rows: uint16(session.info.Height), + Cols: uint16(session.info.Width), + }); err != nil { + log.Printf("[ERROR] NewPTY: Failed to set PTY size: %v", err) + ptmx.Close() + cmd.Process.Kill() + return nil, fmt.Errorf("failed to set PTY size: %w", err) + } + + streamOut, err := os.Create(session.StreamOutPath()) + if err != nil { + log.Printf("[ERROR] NewPTY: Failed to create stream-out: %v", err) + ptmx.Close() + cmd.Process.Kill() + return nil, fmt.Errorf("failed to create stream-out: %w", err) + } + + streamWriter := protocol.NewStreamWriter(streamOut, &protocol.AsciinemaHeader{ + Version: 2, + Width: uint32(session.info.Width), + Height: uint32(session.info.Height), + Command: strings.Join(cmdline, " "), + Env: session.info.Env, + }) + + if err := streamWriter.WriteHeader(); err != nil { + log.Printf("[ERROR] NewPTY: Failed to write stream header: %v", err) + streamOut.Close() + ptmx.Close() + cmd.Process.Kill() + return nil, fmt.Errorf("failed to write stream header: %w", err) + } + + stdinPath := session.StdinPath() + log.Printf("[DEBUG] NewPTY: Creating stdin FIFO at: %s", stdinPath) + if err := syscall.Mkfifo(stdinPath, 0600); err != nil { + log.Printf("[ERROR] NewPTY: Failed to create stdin pipe: %v", err) + streamOut.Close() + ptmx.Close() + cmd.Process.Kill() + return nil, fmt.Errorf("failed to create stdin pipe: %w", err) + } + + return &PTY{ + session: session, + cmd: cmd, + pty: ptmx, + streamWriter: streamWriter, + }, nil +} + +func (p *PTY) Pid() int { + if p.cmd.Process != nil { + return p.cmd.Process.Pid + } + return 0 +} + +func (p *PTY) Run() error { + defer p.Close() + + log.Printf("[DEBUG] PTY.Run: Starting PTY run for session %s, PID %d", p.session.ID[:8], p.cmd.Process.Pid) + + stdinPipe, err := os.OpenFile(p.session.StdinPath(), os.O_RDONLY|syscall.O_NONBLOCK, 0) + if err != nil { + log.Printf("[ERROR] PTY.Run: Failed to open stdin pipe: %v", err) + return fmt.Errorf("failed to open stdin pipe: %w", err) + } + defer stdinPipe.Close() + p.stdinPipe = stdinPipe + + log.Printf("[DEBUG] PTY.Run: Stdin pipe opened successfully") + + errCh := make(chan error, 3) + + go func() { + log.Printf("[DEBUG] PTY.Run: Starting output reading goroutine") + buf := make([]byte, 32*1024) + + for { + // Use a timeout-based approach for cross-platform compatibility + // This avoids the complexity of non-blocking I/O syscalls + n, err := p.pty.Read(buf) + if n > 0 { + log.Printf("[DEBUG] PTY.Run: Read %d bytes of output from PTY", n) + if err := p.streamWriter.WriteOutput(buf[:n]); err != nil { + log.Printf("[ERROR] PTY.Run: Failed to write output: %v", err) + errCh <- fmt.Errorf("failed to write output: %w", err) + return + } + // Continue reading immediately if we got data + continue + } + if err != nil { + if err == io.EOF { + // For blocking reads, EOF typically means the process exited + log.Printf("[DEBUG] PTY.Run: PTY reached EOF, process likely exited") + return + } + // For other errors, this is a problem + log.Printf("[ERROR] PTY.Run: OUTPUT GOROUTINE sending error to errCh: %v", err) + errCh <- fmt.Errorf("PTY read error: %w", err) + return + } + // If we get here, n == 0 and err == nil, which is unusual for blocking reads + // Give a very brief pause to prevent tight loop + time.Sleep(1 * time.Millisecond) + } + }() + + go func() { + log.Printf("[DEBUG] PTY.Run: Starting stdin reading goroutine") + buf := make([]byte, 4096) + for { + n, err := stdinPipe.Read(buf) + if n > 0 { + log.Printf("[DEBUG] PTY.Run: Read %d bytes from stdin, writing to PTY", n) + if _, err := p.pty.Write(buf[:n]); err != nil { + log.Printf("[ERROR] PTY.Run: Failed to write to PTY: %v", err) + // Only exit if the PTY is really broken, not on temporary errors + if err != syscall.EPIPE && err != syscall.ECONNRESET { + errCh <- fmt.Errorf("failed to write to PTY: %w", err) + return + } + // For broken pipe, just continue - the PTY might be closing + log.Printf("[DEBUG] PTY.Run: PTY write failed with pipe error, continuing...") + time.Sleep(10 * time.Millisecond) + } + // Continue immediately after successful write + continue + } + if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { + // No data available, brief pause to prevent CPU spinning + time.Sleep(100 * time.Microsecond) + continue + } + if err == io.EOF { + // No writers to the FIFO yet, brief pause before retry + time.Sleep(500 * time.Microsecond) + continue + } + if err != nil { + // Log other errors but don't crash the session - stdin issues shouldn't kill the PTY + log.Printf("[WARN] PTY.Run: Stdin read error (non-fatal): %v", err) + time.Sleep(1 * time.Millisecond) + continue + } + } + }() + + go func() { + log.Printf("[DEBUG] PTY.Run: Starting process wait goroutine for PID %d", p.cmd.Process.Pid) + err := p.cmd.Wait() + log.Printf("[DEBUG] PTY.Run: Process wait completed for PID %d, error: %v", p.cmd.Process.Pid, err) + + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + exitCode := status.ExitStatus() + p.session.info.ExitCode = &exitCode + log.Printf("[DEBUG] PTY.Run: Process exited with code %d", exitCode) + } + } else { + log.Printf("[DEBUG] PTY.Run: Process exited with non-exit error: %v", err) + } + } else { + exitCode := 0 + p.session.info.ExitCode = &exitCode + log.Printf("[DEBUG] PTY.Run: Process exited normally (code 0)") + } + p.session.info.Status = string(StatusExited) + p.session.info.Save(p.session.Path()) + log.Printf("[DEBUG] PTY.Run: PROCESS WAIT GOROUTINE sending completion to errCh") + errCh <- err + }() + + log.Printf("[DEBUG] PTY.Run: Waiting for first error from goroutines...") + result := <-errCh + log.Printf("[DEBUG] PTY.Run: Received error from goroutine: %v", result) + log.Printf("[DEBUG] PTY.Run: Process PID %d status after error: alive=%v", p.cmd.Process.Pid, p.session.IsAlive()) + return result +} + +func (p *PTY) Attach() error { + if !term.IsTerminal(int(os.Stdin.Fd())) { + return fmt.Errorf("not a terminal") + } + + oldState, err := term.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + return fmt.Errorf("failed to set raw mode: %w", err) + } + p.oldState = oldState + + defer func() { + term.Restore(int(os.Stdin.Fd()), oldState) + }() + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + go func() { + for range ch { + p.updateSize() + } + }() + defer signal.Stop(ch) + + p.updateSize() + + errCh := make(chan error, 2) + + go func() { + _, err := io.Copy(p.pty, os.Stdin) + errCh <- err + }() + + go func() { + _, err := io.Copy(os.Stdout, p.pty) + errCh <- err + }() + + return <-errCh +} + +func (p *PTY) updateSize() error { + if !term.IsTerminal(int(os.Stdin.Fd())) { + return nil + } + + width, height, err := term.GetSize(int(os.Stdin.Fd())) + if err != nil { + return err + } + + return pty.Setsize(p.pty, &pty.Winsize{ + Rows: uint16(height), + Cols: uint16(width), + }) +} + +func (p *PTY) Resize(width, height int) error { + if p.pty == nil { + return fmt.Errorf("PTY not initialized") + } + + p.resizeMutex.Lock() + defer p.resizeMutex.Unlock() + + log.Printf("[DEBUG] PTY.Resize: Resizing PTY to %dx%d for session %s", width, height, p.session.ID[:8]) + + // Resize the actual PTY + err := pty.Setsize(p.pty, &pty.Winsize{ + Rows: uint16(height), + Cols: uint16(width), + }) + + if err != nil { + log.Printf("[ERROR] PTY.Resize: Failed to resize PTY: %v", err) + return fmt.Errorf("failed to resize PTY: %w", err) + } + + // Write resize event to stream if streamWriter is available + if p.streamWriter != nil { + if err := p.streamWriter.WriteResize(uint32(width), uint32(height)); err != nil { + log.Printf("[ERROR] PTY.Resize: Failed to write resize event: %v", err) + // Don't fail the resize operation if we can't write the event + } + } + + log.Printf("[DEBUG] PTY.Resize: Successfully resized PTY to %dx%d", width, height) + return nil +} + +func (p *PTY) Close() error { + if p.streamWriter != nil { + p.streamWriter.Close() + } + if p.pty != nil { + p.pty.Close() + } + if p.oldState != nil { + term.Restore(int(os.Stdin.Fd()), p.oldState) + } + return nil +} \ No newline at end of file diff --git a/linux/pkg/session/session.go b/linux/pkg/session/session.go new file mode 100644 index 00000000..9bdb0234 --- /dev/null +++ b/linux/pkg/session/session.go @@ -0,0 +1,359 @@ +package session + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "sync" + "syscall" + "time" + + "github.com/google/uuid" +) + +type Status string + +const ( + StatusStarting Status = "starting" + StatusRunning Status = "running" + StatusExited Status = "exited" +) + +type Config struct { + Name string + Cmdline []string + Cwd string + Env []string + Width int + Height int +} + +type Info struct { + ID string `json:"id"` + Name string `json:"name"` + Cmdline string `json:"cmdline"` + Cwd string `json:"cwd"` + Pid int `json:"pid,omitempty"` + Status string `json:"status"` + ExitCode *int `json:"exit_code,omitempty"` + StartedAt time.Time `json:"started_at"` + Term string `json:"term"` + Width int `json:"width"` + Height int `json:"height"` + Env map[string]string `json:"env,omitempty"` + Args []string `json:"-"` // Internal use only +} + +type Session struct { + ID string + controlPath string + info *Info + pty *PTY + stdinPipe *os.File + stdinMutex sync.Mutex +} + +func newSession(controlPath string, config Config) (*Session, error) { + id := uuid.New().String() + sessionPath := filepath.Join(controlPath, id) + + log.Printf("[DEBUG] Creating new session %s with config: Name=%s, Cmdline=%v, Cwd=%s", + id[:8], config.Name, config.Cmdline, config.Cwd) + + if err := os.MkdirAll(sessionPath, 0755); err != nil { + return nil, fmt.Errorf("failed to create session directory: %w", err) + } + + if config.Name == "" { + config.Name = id[:8] + } + + // Set default command if empty + if len(config.Cmdline) == 0 { + shell := os.Getenv("SHELL") + if shell == "" { + shell = "/bin/bash" + } + config.Cmdline = []string{shell} + log.Printf("[DEBUG] Session %s: Set default command to %v", id[:8], config.Cmdline) + } + + // Set default working directory if empty + if config.Cwd == "" { + cwd, err := os.Getwd() + if err != nil { + config.Cwd = os.Getenv("HOME") + if config.Cwd == "" { + config.Cwd = "/" + } + } else { + config.Cwd = cwd + } + log.Printf("[DEBUG] Session %s: Set default working directory to %s", id[:8], config.Cwd) + } + + term := os.Getenv("TERM") + if term == "" { + term = "xterm-256color" + } + + // Set default terminal dimensions if not provided + width := config.Width + if width <= 0 { + width = 120 // Better default for modern terminals + } + height := config.Height + if height <= 0 { + height = 30 // Better default for modern terminals + } + + info := &Info{ + ID: id, + Name: config.Name, + Cmdline: strings.Join(config.Cmdline, " "), + Cwd: config.Cwd, + Status: string(StatusStarting), + StartedAt: time.Now(), + Term: term, + Width: width, + Height: height, + Args: config.Cmdline, + } + + if err := info.Save(sessionPath); err != nil { + os.RemoveAll(sessionPath) + return nil, fmt.Errorf("failed to save session info: %w", err) + } + + return &Session{ + ID: id, + controlPath: controlPath, + info: info, + }, nil +} + +func loadSession(controlPath, id string) (*Session, error) { + sessionPath := filepath.Join(controlPath, id) + info, err := LoadInfo(sessionPath) + if err != nil { + return nil, err + } + + session := &Session{ + ID: id, + controlPath: controlPath, + info: info, + } + + // If session is running, we need to reconnect to the PTY for operations like resize + // For now, we'll handle this by checking if we need PTY access in individual methods + + return session, nil +} + +func (s *Session) Path() string { + return filepath.Join(s.controlPath, s.ID) +} + +func (s *Session) StreamOutPath() string { + return filepath.Join(s.Path(), "stream-out") +} + +func (s *Session) StdinPath() string { + return filepath.Join(s.Path(), "stdin") +} + +func (s *Session) NotificationPath() string { + return filepath.Join(s.Path(), "notification-stream") +} + +func (s *Session) Start() error { + pty, err := NewPTY(s) + if err != nil { + return fmt.Errorf("failed to create PTY: %w", err) + } + + s.pty = pty + s.info.Status = string(StatusRunning) + s.info.Pid = pty.Pid() + + if err := s.info.Save(s.Path()); err != nil { + pty.Close() + return fmt.Errorf("failed to update session info: %w", err) + } + + go func() { + if err := s.pty.Run(); err != nil { + log.Printf("[DEBUG] Session %s: PTY.Run() exited with error: %v", s.ID[:8], err) + } else { + log.Printf("[DEBUG] Session %s: PTY.Run() exited normally", s.ID[:8]) + } + }() + + // Process status will be checked on first access - no artificial delay needed + log.Printf("[DEBUG] Session %s: Started successfully", s.ID[:8]) + + return nil +} + +func (s *Session) Attach() error { + if s.pty == nil { + return fmt.Errorf("session not started") + } + return s.pty.Attach() +} + +func (s *Session) SendKey(key string) error { + return s.sendInput([]byte(key)) +} + +func (s *Session) SendText(text string) error { + return s.sendInput([]byte(text)) +} + +func (s *Session) sendInput(data []byte) error { + s.stdinMutex.Lock() + defer s.stdinMutex.Unlock() + + // Open pipe if not already open + if s.stdinPipe == nil { + stdinPath := s.StdinPath() + pipe, err := os.OpenFile(stdinPath, os.O_WRONLY, 0) + if err != nil { + return fmt.Errorf("failed to open stdin pipe: %w", err) + } + s.stdinPipe = pipe + } + + _, err := s.stdinPipe.Write(data) + if err != nil { + // If write fails, close and reset the pipe for next attempt + s.stdinPipe.Close() + s.stdinPipe = nil + return fmt.Errorf("failed to write to stdin pipe: %w", err) + } + return nil +} + +func (s *Session) Signal(sig string) error { + if s.info.Pid == 0 { + return fmt.Errorf("no process to signal") + } + + proc, err := os.FindProcess(s.info.Pid) + if err != nil { + return err + } + + switch sig { + case "SIGTERM": + return proc.Signal(os.Interrupt) + case "SIGKILL": + return proc.Kill() + default: + return fmt.Errorf("unsupported signal: %s", sig) + } +} + +func (s *Session) Stop() error { + return s.Signal("SIGTERM") +} + +func (s *Session) Kill() error { + err := s.Signal("SIGKILL") + s.cleanup() + return err +} + +func (s *Session) cleanup() { + s.stdinMutex.Lock() + defer s.stdinMutex.Unlock() + + if s.stdinPipe != nil { + s.stdinPipe.Close() + s.stdinPipe = nil + } +} + +func (s *Session) Resize(width, height int) error { + if s.pty == nil { + return fmt.Errorf("session not started") + } + + // Check if session is still alive + if s.info.Status == string(StatusExited) { + return fmt.Errorf("cannot resize exited session") + } + + // Validate dimensions + if width <= 0 || height <= 0 { + return fmt.Errorf("invalid dimensions: width=%d, height=%d", width, height) + } + + // Update session info + s.info.Width = width + s.info.Height = height + + // Save updated session info + if err := s.info.Save(s.Path()); err != nil { + log.Printf("[ERROR] Failed to save session info after resize: %v", err) + } + + // Resize the PTY + return s.pty.Resize(width, height) +} + +func (s *Session) IsAlive() bool { + if s.info.Pid == 0 { + return false + } + + proc, err := os.FindProcess(s.info.Pid) + if err != nil { + return false + } + + err = proc.Signal(syscall.Signal(0)) + return err == nil +} + +func (s *Session) UpdateStatus() error { + if s.info.Status == string(StatusExited) { + return nil + } + + if !s.IsAlive() { + s.info.Status = string(StatusExited) + exitCode := 0 + s.info.ExitCode = &exitCode + return s.info.Save(s.Path()) + } + + return nil +} + +func (i *Info) Save(sessionPath string) error { + data, err := json.MarshalIndent(i, "", " ") + if err != nil { + return err + } + + return os.WriteFile(filepath.Join(sessionPath, "session.json"), data, 0644) +} + +func LoadInfo(sessionPath string) (*Info, error) { + data, err := os.ReadFile(filepath.Join(sessionPath, "session.json")) + if err != nil { + return nil, err + } + + var info Info + if err := json.Unmarshal(data, &info); err != nil { + return nil, err + } + + return &info, nil +} \ No newline at end of file diff --git a/linux/vibetunnel b/linux/vibetunnel new file mode 100755 index 00000000..19077a41 Binary files /dev/null and b/linux/vibetunnel differ diff --git a/linux/vibetunnel-tls b/linux/vibetunnel-tls new file mode 100755 index 00000000..69b46e3b Binary files /dev/null and b/linux/vibetunnel-tls differ diff --git a/web/package-lock.json b/web/package-lock.json index ee6d74e9..7f3cd79d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -138,16 +138,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.27.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", @@ -182,14 +172,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/@babel/helper-module-imports": { @@ -577,16 +567,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", @@ -645,74 +625,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", @@ -730,346 +642,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -1114,30 +686,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/config-helpers": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", @@ -1185,15 +733,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@eslint/eslintrc/node_modules/ignore": { @@ -1206,19 +756,6 @@ "node": ">= 4" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { "version": "9.29.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", @@ -1353,6 +890,35 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1380,20 +946,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -1408,48 +960,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1543,19 +1053,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/core/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -2056,111 +1553,17 @@ "node": ">=18" } }, - "node_modules/@puppeteer/browsers/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@puppeteer/browsers/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@puppeteer/browsers/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@puppeteer/browsers/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" } }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -2682,19 +2085,6 @@ "pretty-format": "^30.0.0" } }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@types/jest/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -3053,6 +2443,45 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/utils": { "version": "8.34.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", @@ -3418,6 +2847,47 @@ } } }, + "node_modules/@vitest/coverage-v8/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -3574,6 +3044,27 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3651,16 +3142,13 @@ } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -3858,67 +3346,6 @@ "node": ">=12" } }, - "node_modules/babel-plugin-istanbul/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-jest-hoist": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.0.tgz", @@ -4106,13 +3533,14 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -4334,6 +3762,35 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -4389,6 +3846,29 @@ "node": ">= 8.10.0" } }, + "node_modules/chokidar-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chokidar-cli/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4414,6 +3894,55 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar-cli/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/chokidar-cli/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chokidar-cli/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar-cli/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/chokidar-cli/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -4427,6 +3956,69 @@ "node": ">= 6" } }, + "node_modules/chokidar-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chokidar-cli/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chokidar-cli/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4440,6 +4032,86 @@ "node": ">=8.10.0" } }, + "node_modules/chokidar-cli/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/chokidar-cli/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/chokidar-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "node_modules/chromium-bidi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", @@ -4510,6 +4182,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/cli-truncate/node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -4535,116 +4220,91 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/cliui/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cliui/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, "node_modules/cliui/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/cliui/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/co": { @@ -4706,13 +4366,13 @@ } }, "node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": ">= 6" } }, "node_modules/component-emitter": { @@ -4758,129 +4418,6 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/concurrently/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/concurrently/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/concurrently/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/concurrently/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/concurrently/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/concurrently/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -4910,9 +4447,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5205,9 +4742,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.170", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz", - "integrity": "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==", + "version": "1.5.167", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", + "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", "dev": true, "license": "ISC" }, @@ -5394,16 +4931,13 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/escodegen": { @@ -5566,15 +5100,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { @@ -5590,6 +5126,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5600,17 +5153,36 @@ "node": ">= 4" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "p-locate": "^5.0.0" }, "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/espree": { @@ -5754,13 +5326,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/exit-x": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", @@ -5841,6 +5406,27 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -6034,6 +5620,16 @@ "minimatch": "^5.0.1" } }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -6078,20 +5674,17 @@ } }, "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", + "locate-path": "^5.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/flat-cache": { @@ -6132,6 +5725,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", @@ -6149,29 +5755,6 @@ "node": ">= 6" } }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -6431,17 +6014,40 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, "node_modules/gopd": { @@ -6795,16 +6401,13 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/is-generator-fn": { @@ -6893,6 +6496,19 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -6908,6 +6524,19 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", @@ -6972,30 +6601,6 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/jest": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.0.tgz", @@ -7070,19 +6675,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-circus/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7138,113 +6730,6 @@ } } }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.0.tgz", @@ -7297,19 +6782,6 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-config/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7348,19 +6820,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-diff/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7413,19 +6872,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-each/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7506,19 +6952,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-leak-detector/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7557,19 +6990,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7613,19 +7033,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-message-util/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7826,19 +7233,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-snapshot/node_modules/pretty-format": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", @@ -7861,6 +7255,19 @@ "dev": true, "license": "MIT" }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-util": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.0.tgz", @@ -7910,19 +7317,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -7995,22 +7389,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -8197,6 +7575,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", @@ -8215,6 +7603,19 @@ "node": ">=18.0.0" } }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/listr2/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -8253,6 +7654,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/listr2/node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -8303,19 +7720,16 @@ } }, "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/lodash": { @@ -8389,6 +7803,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/log-update/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -8460,6 +7887,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -8486,13 +7929,13 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "engines": { + "node": ">=12" } }, "node_modules/lz-string": { @@ -8543,6 +7986,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -8645,21 +8101,23 @@ } }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -8689,19 +8147,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minipass": { @@ -9030,16 +8485,29 @@ } }, "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9297,62 +8765,6 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -9557,19 +8969,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -9613,16 +9012,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -9913,6 +9302,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10055,16 +9457,13 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -10089,6 +9488,27 @@ "node": ">= 18" } }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -10233,17 +9653,11 @@ "license": "ISC" }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/sirv": { "version": "3.0.1", @@ -10300,6 +9714,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -10392,16 +9819,6 @@ "node": ">=10" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -10463,19 +9880,6 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -10517,30 +9921,20 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=12" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/strip-ansi": { + "node_modules/string-width/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", @@ -10556,8 +9950,7 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -10570,17 +9963,18 @@ "node": ">=8" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, "node_modules/strip-bom": { @@ -10659,16 +10053,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/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, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/superagent": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.1.tgz", @@ -10705,16 +10089,19 @@ } }, "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -10863,18 +10250,40 @@ } }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-decoder": { @@ -11121,6 +10530,19 @@ } } }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -11211,6 +10633,27 @@ "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-query-selector": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", @@ -11721,6 +11164,22 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11728,16 +11187,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -11753,17 +11202,17 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { @@ -11779,6 +11228,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11799,6 +11264,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ws": { "version": "8.18.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", @@ -11828,11 +11306,14 @@ "license": "MIT" }, "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { "version": "3.1.1", @@ -11855,22 +11336,22 @@ } }, "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -11883,136 +11364,26 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "node": ">=8" } }, "node_modules/yauzl": { diff --git a/web/src/client/components/session-create-form.ts b/web/src/client/components/session-create-form.ts index a88b512a..c4116419 100644 --- a/web/src/client/components/session-create-form.ts +++ b/web/src/client/components/session-create-form.ts @@ -7,6 +7,8 @@ export interface SessionCreateData { workingDir: string; name?: string; spawn_terminal?: boolean; + width?: number; + height?: number; } @customElement('session-create-form') @@ -128,10 +130,17 @@ export class SessionCreateForm extends LitElement { this.isCreating = true; + // Use conservative defaults that work well across devices + // The terminal will auto-resize to fit the actual container after creation + const terminalWidth = 120; + const terminalHeight = 30; + const sessionData: SessionCreateData = { command: this.parseCommand(this.command.trim()), workingDir: this.workingDir.trim(), spawn_terminal: true, + width: terminalWidth, + height: terminalHeight, }; // Add session name if provided diff --git a/web/src/client/components/session-view.ts b/web/src/client/components/session-view.ts index 85d083fb..bb13c02b 100644 --- a/web/src/client/components/session-view.ts +++ b/web/src/client/components/session-view.ts @@ -33,6 +33,9 @@ export class SessionView extends LitElement { private loadingInterval: number | null = null; private keyboardListenerAdded = false; private touchListenersAdded = false; + private resizeTimeout: number | null = null; + private lastResizeWidth = 0; + private lastResizeHeight = 0; private keyboardHandler = (e: KeyboardEvent) => { if (!this.session) return; @@ -281,6 +284,47 @@ export class SessionView extends LitElement { originalEventSource.addEventListener('error', handleError); this.streamConnection = connection; + + // After connecting, ensure the backend session matches the terminal's actual dimensions + // TODO: Re-enable once terminal properly calculates dimensions + // this.syncTerminalDimensions(); + } + + private async syncTerminalDimensions() { + if (!this.terminal || !this.session) return; + + // Wait a moment for terminal to be fully initialized + setTimeout(async () => { + if (!this.terminal || !this.session) return; + + // Don't sync if the terminal hasn't been properly fitted yet + // The terminal component should emit resize events when it's properly sized + const cols = this.terminal.cols || 80; + const rows = this.terminal.rows || 24; + + // Only sync if the dimensions are significantly different (avoid minor differences) + // and avoid syncing the default 80x24 dimensions + const colsDiff = Math.abs(cols - (this.session.width || 120)); + const rowsDiff = Math.abs(rows - (this.session.height || 30)); + + if ((colsDiff > 5 || rowsDiff > 5) && !(cols === 80 && rows === 24)) { + console.log(`Syncing terminal dimensions: ${cols}x${rows} (was ${this.session.width}x${this.session.height})`); + + try { + const response = await fetch(`/api/sessions/${this.session.id}/resize`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ width: cols, height: rows }), + }); + + if (!response.ok) { + console.warn(`Failed to sync terminal dimensions: ${response.status}`); + } + } catch (error) { + console.warn('Failed to sync terminal dimensions:', error); + } + } + }, 1000); } private async handleKeyboardInput(e: KeyboardEvent) { @@ -465,12 +509,48 @@ export class SessionView extends LitElement { } } - private handleTerminalResize(event: CustomEvent) { + private async handleTerminalResize(event: CustomEvent) { // Update terminal dimensions for display const { cols, rows } = event.detail; this.terminalCols = cols; this.terminalRows = rows; this.requestUpdate(); + + // Debounce resize requests to prevent jumpiness + if (this.resizeTimeout) { + clearTimeout(this.resizeTimeout); + } + + this.resizeTimeout = setTimeout(async () => { + // Only send resize request if dimensions actually changed + if (cols === this.lastResizeWidth && rows === this.lastResizeHeight) { + console.log(`Skipping redundant resize request: ${cols}x${rows}`); + return; + } + + // Send resize request to backend if session is active + if (this.session && this.session.status !== 'exited') { + try { + console.log(`Sending resize request: ${cols}x${rows} (was ${this.lastResizeWidth}x${this.lastResizeHeight})`); + + const response = await fetch(`/api/sessions/${this.session.id}/resize`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ width: cols, height: rows }), + }); + + if (response.ok) { + // Cache the successfully sent dimensions + this.lastResizeWidth = cols; + this.lastResizeHeight = rows; + } else { + console.warn(`Failed to resize session: ${response.status}`); + } + } catch (error) { + console.warn('Failed to send resize request:', error); + } + } + }, 250); // 250ms debounce delay } // Mobile input methods diff --git a/web/src/client/components/terminal.ts b/web/src/client/components/terminal.ts index ce733bf3..a3035728 100644 --- a/web/src/client/components/terminal.ts +++ b/web/src/client/components/terminal.ts @@ -39,7 +39,6 @@ export class Terminal extends LitElement { @state() private actualRows = 24; // Rows that fit in viewport private container: HTMLElement | null = null; - private resizeObserver: ResizeObserver | null = null; private resizeTimeout: NodeJS.Timeout | null = null; // Virtual scrolling optimization @@ -102,10 +101,7 @@ export class Terminal extends LitElement { this.momentumAnimation = null; } - if (this.resizeObserver) { - this.resizeObserver.disconnect(); - this.resizeObserver = null; - } + // ResizeObserver cleanup removed - we only use window resize events now if (this.terminal) { this.terminal.dispose(); this.terminal = null; @@ -176,22 +172,47 @@ export class Terminal extends LitElement { try { // Create regular terminal but don't call .open() to make it headless this.terminal = new XtermTerminal({ - cursorBlink: false, + cursorBlink: true, + cursorStyle: 'block', + cursorWidth: 1, lineHeight: 1.2, + letterSpacing: 0, scrollback: 10000, allowProposedApi: true, + allowTransparency: false, + convertEol: true, + drawBoldTextInBrightColors: true, + fontWeightBold: 'bold', + minimumContrastRatio: 1, + macOptionIsMeta: true, + altClickMovesCursor: true, + rightClickSelectsWord: false, + wordSeparator: ' ()[]{}\'"`', theme: { background: '#1e1e1e', foreground: '#d4d4d4', cursor: '#00ff00', + cursorAccent: '#1e1e1e', + selectionBackground: '#264f78', + selectionForeground: '#ffffff', + selectionInactiveBackground: '#3a3a3a', + // Standard 16 colors (0-15) - using proper xterm colors black: '#000000', - red: '#f14c4c', - green: '#23d18b', - yellow: '#f5f543', - blue: '#3b8eea', - magenta: '#d670d6', - cyan: '#29b8db', + red: '#cd0000', + green: '#00cd00', + yellow: '#cdcd00', + blue: '#0000ee', + magenta: '#cd00cd', + cyan: '#00cdcd', white: '#e5e5e5', + brightBlack: '#7f7f7f', + brightRed: '#ff0000', + brightGreen: '#00ff00', + brightYellow: '#ffff00', + brightBlue: '#5c5cff', + brightMagenta: '#ff00ff', + brightCyan: '#00ffff', + brightWhite: '#ffffff', }, }); @@ -214,7 +235,7 @@ export class Terminal extends LitElement { measureEl.style.top = '0'; measureEl.style.left = '0'; measureEl.style.fontSize = `${this.fontSize}px`; - measureEl.style.fontFamily = 'Fira Code, monospace'; + measureEl.style.fontFamily = 'Hack Nerd Font Mono, Fira Code, monospace'; // Use a mix of characters that represent typical terminal content const testString = @@ -268,14 +289,49 @@ export class Terminal extends LitElement { // Resize the terminal to the new dimensions if (this.terminal) { this.terminal.resize(this.cols, this.rows); + + // Dispatch resize event for backend synchronization + this.dispatchEvent( + new CustomEvent('terminal-resize', { + detail: { cols: this.cols, rows: this.rows }, + bubbles: true, + }) + ); } } else { - // Normal mode: just calculate how many rows fit in the viewport + // Normal mode: calculate both cols and rows based on container size + const containerWidth = this.container.clientWidth; const containerHeight = this.container.clientHeight; const lineHeight = this.fontSize * 1.2; - const newActualRows = Math.max(1, Math.floor(containerHeight / lineHeight)); - - this.actualRows = newActualRows; + const charWidth = this.measureCharacterWidth(); + + const newCols = Math.max(20, Math.floor(containerWidth / charWidth)); + const newRows = Math.max(6, Math.floor(containerHeight / lineHeight)); + + // Update logical dimensions if they changed significantly + const colsChanged = Math.abs(newCols - this.cols) > 3; + const rowsChanged = Math.abs(newRows - this.rows) > 2; + + if (colsChanged || rowsChanged) { + this.cols = newCols; + this.rows = newRows; + this.actualRows = newRows; + + // Resize the terminal to the new dimensions + if (this.terminal) { + this.terminal.resize(this.cols, this.rows); + + // Dispatch resize event for backend synchronization + this.dispatchEvent( + new CustomEvent('terminal-resize', { + detail: { cols: this.cols, rows: this.rows }, + bubbles: true, + }) + ); + } + } else { + this.actualRows = newRows; + } } // Recalculate viewportY based on new lineHeight and actualRows @@ -303,19 +359,23 @@ export class Terminal extends LitElement { private setupResize() { if (!this.container) return; - this.resizeObserver = new ResizeObserver(() => { - if (this.resizeTimeout) { - clearTimeout(this.resizeTimeout); - } - this.resizeTimeout = setTimeout(() => { - this.fitTerminal(); - }, 50); - }); - this.resizeObserver.observe(this.container); - + // Only listen to window resize events to avoid pixel-level jitter + // Use debounced handling to prevent resize spam + let windowResizeTimeout: number | null = null; + window.addEventListener('resize', () => { - this.fitTerminal(); + if (windowResizeTimeout) { + clearTimeout(windowResizeTimeout); + } + windowResizeTimeout = setTimeout(() => { + this.fitTerminal(); + }, 150); // Debounce window resize events }); + + // Do an initial fit when the terminal is first set up + setTimeout(() => { + this.fitTerminal(); + }, 100); } private setupScrolling() { @@ -738,11 +798,34 @@ export class Terminal extends LitElement { const isItalic = cell.isItalic(); const isUnderline = cell.isUnderline(); const isDim = cell.isDim(); + const isInverse = cell.isInverse(); + const isInvisible = cell.isInvisible(); + const isStrikethrough = cell.isStrikethrough(); if (isBold) classes += ' bold'; if (isItalic) classes += ' italic'; if (isUnderline) classes += ' underline'; if (isDim) classes += ' dim'; + if (isStrikethrough) classes += ' strikethrough'; + + // Handle inverse colors + if (isInverse) { + // Swap foreground and background colors + const tempFg = style.match(/color: ([^;]+);/)?.[1]; + const tempBg = style.match(/background-color: ([^;]+);/)?.[1]; + if (tempFg && tempBg) { + style = style.replace(/color: [^;]+;/, `color: ${tempBg};`); + style = style.replace(/background-color: [^;]+;/, `background-color: ${tempFg};`); + } else if (tempFg) { + style = style.replace(/color: [^;]+;/, 'color: #1e1e1e;'); + style += `background-color: ${tempFg};`; + } + } + + // Handle invisible text + if (isInvisible) { + style += 'opacity: 0;'; + } // Check if styling changed - if so, flush current group if (classes !== currentClasses || style !== currentStyle) { diff --git a/web/src/client/utils/xterm-colors.ts b/web/src/client/utils/xterm-colors.ts new file mode 100644 index 00000000..879fc24f --- /dev/null +++ b/web/src/client/utils/xterm-colors.ts @@ -0,0 +1,37 @@ +// XTerm 256-color palette generator +export function generateXTermColorCSS(): string { + const colors: string[] = []; + + // Standard 16 colors (0-15) + const standard16 = [ + '#000000', '#800000', '#008000', '#808000', '#000080', '#800080', '#008080', '#c0c0c0', + '#808080', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff' + ]; + + standard16.forEach((color, i) => { + colors.push(` --terminal-color-${i}: ${color};`); + }); + + // 216 color cube (16-231) + const cube = [0, 95, 135, 175, 215, 255]; + for (let r = 0; r < 6; r++) { + for (let g = 0; g < 6; g++) { + for (let b = 0; b < 6; b++) { + const index = 16 + r * 36 + g * 6 + b; + const red = cube[r].toString(16).padStart(2, '0'); + const green = cube[g].toString(16).padStart(2, '0'); + const blue = cube[b].toString(16).padStart(2, '0'); + colors.push(` --terminal-color-${index}: #${red}${green}${blue};`); + } + } + } + + // Grayscale (232-255) + for (let i = 0; i < 24; i++) { + const gray = Math.round(8 + i * 10); + const hex = gray.toString(16).padStart(2, '0'); + colors.push(` --terminal-color-${232 + i}: #${hex}${hex}${hex};`); + } + + return `:root {\n${colors.join('\n')}\n}`; +} \ No newline at end of file diff --git a/web/src/input.css b/web/src/input.css index f3b8281b..c3e0f768 100644 --- a/web/src/input.css +++ b/web/src/input.css @@ -14,10 +14,27 @@ font-variation-settings: 'wght' 400; } -/* Override Tailwind's font-mono to use Fira Code */ +/* Hack Nerd Font */ +@font-face { + font-family: 'Hack Nerd Font Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/fonts/HackNerdFontMono-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Hack Nerd Font Mono'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('/fonts/HackNerdFontMono-Bold.ttf') format('truetype'); +} + +/* Override Tailwind's font-mono to use Hack Nerd Font */ .font-mono { font-family: - 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, + 'Hack Nerd Font Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace !important; } @@ -81,8 +98,55 @@ body { .xterm { padding: 0 !important; font-family: - 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, + 'Hack Nerd Font Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace !important; + font-variant-ligatures: none; + font-feature-settings: "liga" 0, "clig" 0, "calt" 0; + text-rendering: optimizeSpeed; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Terminal character specific styling */ +.terminal-char { + font-variant-ligatures: none; + font-feature-settings: "liga" 0; + white-space: pre; +} + +/* Terminal text decoration support */ +.terminal-char.bold { + font-weight: bold; +} + +.terminal-char.italic { + font-style: italic; +} + +.terminal-char.underline { + text-decoration: underline; +} + +.terminal-char.dim { + opacity: 0.5; +} + +.terminal-char.strikethrough { + text-decoration: line-through; +} + +.terminal-char.overline { + text-decoration: overline; +} + +/* Cursor styling */ +.terminal-char.cursor { + animation: blink 1s infinite; +} + +@keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0.3; } } .xterm .xterm-viewport { @@ -140,3 +204,263 @@ body { .xterm .xterm-helper-textarea { opacity: 0 !important; } + +/* XTerm 256-color palette - Complete */ +:root { + --terminal-color-0: #000000; + --terminal-color-1: #800000; + --terminal-color-2: #008000; + --terminal-color-3: #808000; + --terminal-color-4: #000080; + --terminal-color-5: #800080; + --terminal-color-6: #008080; + --terminal-color-7: #c0c0c0; + --terminal-color-8: #808080; + --terminal-color-9: #ff0000; + --terminal-color-10: #00ff00; + --terminal-color-11: #ffff00; + --terminal-color-12: #0000ff; + --terminal-color-13: #ff00ff; + --terminal-color-14: #00ffff; + --terminal-color-15: #ffffff; + --terminal-color-16: #000000; + --terminal-color-17: #00005f; + --terminal-color-18: #000087; + --terminal-color-19: #0000af; + --terminal-color-20: #0000d7; + --terminal-color-21: #0000ff; + --terminal-color-22: #005f00; + --terminal-color-23: #005f5f; + --terminal-color-24: #005f87; + --terminal-color-25: #005faf; + --terminal-color-26: #005fd7; + --terminal-color-27: #005fff; + --terminal-color-28: #008700; + --terminal-color-29: #00875f; + --terminal-color-30: #008787; + --terminal-color-31: #0087af; + --terminal-color-32: #0087d7; + --terminal-color-33: #0087ff; + --terminal-color-34: #00af00; + --terminal-color-35: #00af5f; + --terminal-color-36: #00af87; + --terminal-color-37: #00afaf; + --terminal-color-38: #00afd7; + --terminal-color-39: #00afff; + --terminal-color-40: #00d700; + --terminal-color-41: #00d75f; + --terminal-color-42: #00d787; + --terminal-color-43: #00d7af; + --terminal-color-44: #00d7d7; + --terminal-color-45: #00d7ff; + --terminal-color-46: #00ff00; + --terminal-color-47: #00ff5f; + --terminal-color-48: #00ff87; + --terminal-color-49: #00ffaf; + --terminal-color-50: #00ffd7; + --terminal-color-51: #00ffff; + --terminal-color-52: #5f0000; + --terminal-color-53: #5f005f; + --terminal-color-54: #5f0087; + --terminal-color-55: #5f00af; + --terminal-color-56: #5f00d7; + --terminal-color-57: #5f00ff; + --terminal-color-58: #5f5f00; + --terminal-color-59: #5f5f5f; + --terminal-color-60: #5f5f87; + --terminal-color-61: #5f5faf; + --terminal-color-62: #5f5fd7; + --terminal-color-63: #5f5fff; + --terminal-color-64: #5f8700; + --terminal-color-65: #5f875f; + --terminal-color-66: #5f8787; + --terminal-color-67: #5f87af; + --terminal-color-68: #5f87d7; + --terminal-color-69: #5f87ff; + --terminal-color-70: #5faf00; + --terminal-color-71: #5faf5f; + --terminal-color-72: #5faf87; + --terminal-color-73: #5fafaf; + --terminal-color-74: #5fafd7; + --terminal-color-75: #5fafff; + --terminal-color-76: #5fd700; + --terminal-color-77: #5fd75f; + --terminal-color-78: #5fd787; + --terminal-color-79: #5fd7af; + --terminal-color-80: #5fd7d7; + --terminal-color-81: #5fd7ff; + --terminal-color-82: #5fff00; + --terminal-color-83: #5fff5f; + --terminal-color-84: #5fff87; + --terminal-color-85: #5fffaf; + --terminal-color-86: #5fffd7; + --terminal-color-87: #5fffff; + --terminal-color-88: #870000; + --terminal-color-89: #87005f; + --terminal-color-90: #870087; + --terminal-color-91: #8700af; + --terminal-color-92: #8700d7; + --terminal-color-93: #8700ff; + --terminal-color-94: #875f00; + --terminal-color-95: #875f5f; + --terminal-color-96: #875f87; + --terminal-color-97: #875faf; + --terminal-color-98: #875fd7; + --terminal-color-99: #875fff; + --terminal-color-100: #878700; + --terminal-color-101: #87875f; + --terminal-color-102: #878787; + --terminal-color-103: #8787af; + --terminal-color-104: #8787d7; + --terminal-color-105: #8787ff; + --terminal-color-106: #87af00; + --terminal-color-107: #87af5f; + --terminal-color-108: #87af87; + --terminal-color-109: #87afaf; + --terminal-color-110: #87afd7; + --terminal-color-111: #87afff; + --terminal-color-112: #87d700; + --terminal-color-113: #87d75f; + --terminal-color-114: #87d787; + --terminal-color-115: #87d7af; + --terminal-color-116: #87d7d7; + --terminal-color-117: #87d7ff; + --terminal-color-118: #87ff00; + --terminal-color-119: #87ff5f; + --terminal-color-120: #87ff87; + --terminal-color-121: #87ffaf; + --terminal-color-122: #87ffd7; + --terminal-color-123: #87ffff; + --terminal-color-124: #af0000; + --terminal-color-125: #af005f; + --terminal-color-126: #af0087; + --terminal-color-127: #af00af; + --terminal-color-128: #af00d7; + --terminal-color-129: #af00ff; + --terminal-color-130: #af5f00; + --terminal-color-131: #af5f5f; + --terminal-color-132: #af5f87; + --terminal-color-133: #af5faf; + --terminal-color-134: #af5fd7; + --terminal-color-135: #af5fff; + --terminal-color-136: #af8700; + --terminal-color-137: #af875f; + --terminal-color-138: #af8787; + --terminal-color-139: #af87af; + --terminal-color-140: #af87d7; + --terminal-color-141: #af87ff; + --terminal-color-142: #afaf00; + --terminal-color-143: #afaf5f; + --terminal-color-144: #afaf87; + --terminal-color-145: #afafaf; + --terminal-color-146: #afafd7; + --terminal-color-147: #afafff; + --terminal-color-148: #afd700; + --terminal-color-149: #afd75f; + --terminal-color-150: #afd787; + --terminal-color-151: #afd7af; + --terminal-color-152: #afd7d7; + --terminal-color-153: #afd7ff; + --terminal-color-154: #afff00; + --terminal-color-155: #afff5f; + --terminal-color-156: #afff87; + --terminal-color-157: #afffaf; + --terminal-color-158: #afffd7; + --terminal-color-159: #afffff; + --terminal-color-160: #d70000; + --terminal-color-161: #d7005f; + --terminal-color-162: #d70087; + --terminal-color-163: #d700af; + --terminal-color-164: #d700d7; + --terminal-color-165: #d700ff; + --terminal-color-166: #d75f00; + --terminal-color-167: #d75f5f; + --terminal-color-168: #d75f87; + --terminal-color-169: #d75faf; + --terminal-color-170: #d75fd7; + --terminal-color-171: #d75fff; + --terminal-color-172: #d78700; + --terminal-color-173: #d7875f; + --terminal-color-174: #d78787; + --terminal-color-175: #d787af; + --terminal-color-176: #d787d7; + --terminal-color-177: #d787ff; + --terminal-color-178: #d7af00; + --terminal-color-179: #d7af5f; + --terminal-color-180: #d7af87; + --terminal-color-181: #d7afaf; + --terminal-color-182: #d7afd7; + --terminal-color-183: #d7afff; + --terminal-color-184: #d7d700; + --terminal-color-185: #d7d75f; + --terminal-color-186: #d7d787; + --terminal-color-187: #d7d7af; + --terminal-color-188: #d7d7d7; + --terminal-color-189: #d7d7ff; + --terminal-color-190: #d7ff00; + --terminal-color-191: #d7ff5f; + --terminal-color-192: #d7ff87; + --terminal-color-193: #d7ffaf; + --terminal-color-194: #d7ffd7; + --terminal-color-195: #d7ffff; + --terminal-color-196: #ff0000; + --terminal-color-197: #ff005f; + --terminal-color-198: #ff0087; + --terminal-color-199: #ff00af; + --terminal-color-200: #ff00d7; + --terminal-color-201: #ff00ff; + --terminal-color-202: #ff5f00; + --terminal-color-203: #ff5f5f; + --terminal-color-204: #ff5f87; + --terminal-color-205: #ff5faf; + --terminal-color-206: #ff5fd7; + --terminal-color-207: #ff5fff; + --terminal-color-208: #ff8700; + --terminal-color-209: #ff875f; + --terminal-color-210: #ff8787; + --terminal-color-211: #ff87af; + --terminal-color-212: #ff87d7; + --terminal-color-213: #ff87ff; + --terminal-color-214: #ffaf00; + --terminal-color-215: #ffaf5f; + --terminal-color-216: #ffaf87; + --terminal-color-217: #ffafaf; + --terminal-color-218: #ffafd7; + --terminal-color-219: #ffafff; + --terminal-color-220: #ffd700; + --terminal-color-221: #ffd75f; + --terminal-color-222: #ffd787; + --terminal-color-223: #ffd7af; + --terminal-color-224: #ffd7d7; + --terminal-color-225: #ffd7ff; + --terminal-color-226: #ffff00; + --terminal-color-227: #ffff5f; + --terminal-color-228: #ffff87; + --terminal-color-229: #ffffaf; + --terminal-color-230: #ffffd7; + --terminal-color-231: #ffffff; + --terminal-color-232: #080808; + --terminal-color-233: #121212; + --terminal-color-234: #1c1c1c; + --terminal-color-235: #262626; + --terminal-color-236: #303030; + --terminal-color-237: #3a3a3a; + --terminal-color-238: #444444; + --terminal-color-239: #4e4e4e; + --terminal-color-240: #585858; + --terminal-color-241: #626262; + --terminal-color-242: #6c6c6c; + --terminal-color-243: #767676; + --terminal-color-244: #808080; + --terminal-color-245: #8a8a8a; + --terminal-color-246: #949494; + --terminal-color-247: #9e9e9e; + --terminal-color-248: #a8a8a8; + --terminal-color-249: #b2b2b2; + --terminal-color-250: #bcbcbc; + --terminal-color-251: #c6c6c6; + --terminal-color-252: #d0d0d0; + --terminal-color-253: #dadada; + --terminal-color-254: #e4e4e4; + --terminal-color-255: #eeeeee; +}