diff --git a/registry/kunstewi/.images/avatar.png b/registry/kunstewi/.images/avatar.png new file mode 100644 index 00000000..e918aea2 Binary files /dev/null and b/registry/kunstewi/.images/avatar.png differ diff --git a/registry/kunstewi/README.md b/registry/kunstewi/README.md new file mode 100644 index 00000000..88588d2b --- /dev/null +++ b/registry/kunstewi/README.md @@ -0,0 +1,14 @@ +--- +display_name: Stewi +bio: I build and break things, probably i break things more. +avatar_url: ./.images/avatar.png +github: kunstewi +linkedin: "https://www.linkedin.com/in/kunstewi" +website: "https://kunstewi.tech" +support_email: kunstewi@gmail.com +status: "community" +--- + +# Stewi + +I build and break things, probably i break things more. \ No newline at end of file diff --git a/registry/kunstewi/modules/auto-dev-server/README.md b/registry/kunstewi/modules/auto-dev-server/README.md new file mode 100644 index 00000000..3b8d264f --- /dev/null +++ b/registry/kunstewi/modules/auto-dev-server/README.md @@ -0,0 +1,59 @@ +--- +display_name: Auto Development Server +description: Automatically detect and start development servers based on project detection +icon: ../../../../.icons/play.svg +verified: false +maintainer_github: kunstewi +tags: [development, automation, devcontainer] +--- + +# Auto Development Server + +Automatically detects and starts development servers for various project types when the workspace starts. Supports Node.js, Python, Ruby, Go, Rust, PHP projects, and integrates with devcontainer.json configuration. + +```tf +module "auto_dev_server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/kunstewi/auto-dev-server/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +## Supported Project Types + +- **Node.js**: Detects `package.json` and runs `npm start`, `npm run dev`, or `yarn start` +- **Python**: Detects Django (`manage.py`), Flask, or FastAPI projects +- **Ruby**: Detects Rails applications and Rack applications +- **Go**: Detects `go.mod` or `main.go` files +- **Rust**: Detects `Cargo.toml` files +- **PHP**: Detects `composer.json` or `index.php` files +- **Devcontainer**: Uses `postStartCommand` from `.devcontainer/devcontainer.json` + +## Examples + +### Basic Usage + +```tf +module "auto_dev_server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/kunstewi/auto-dev-server/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +### Custom Configuration + +```tf +module "auto_dev_server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/kunstewi/auto-dev-server/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + project_dir = "/workspace/projects" + port_range_start = 4000 + port_range_end = 8000 + log_level = "DEBUG" +} +``` \ No newline at end of file diff --git a/registry/kunstewi/modules/auto-dev-server/main.test.ts b/registry/kunstewi/modules/auto-dev-server/main.test.ts new file mode 100644 index 00000000..fd73889c --- /dev/null +++ b/registry/kunstewi/modules/auto-dev-server/main.test.ts @@ -0,0 +1,350 @@ +import { expect, it, describe } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, + runContainer, + removeContainer, + execContainer, + writeFileContainer, + readFileContainer, + TerraformState, +} from "../../../../test/test"; + +const moduleDir = import.meta.dir; + +describe("auto-dev-server", () => { + // Test required variables + testRequiredVariables(moduleDir, { + agent_id: "test-agent-id", + }); + + // Test basic module functionality + it("should apply successfully with default values", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + }); + + // Verify the script resource was created + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + expect(scriptResource).toBeDefined(); + expect(scriptResource?.instances[0].attributes.agent_id).toBe("test-agent-id"); + expect(scriptResource?.instances[0].attributes.display_name).toBe("Auto Development Server"); + expect(scriptResource?.instances[0].attributes.run_on_start).toBe(true); + }); + + // Test custom configuration + it("should apply successfully with custom values", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace/projects", + auto_start: false, + port_range_start: 4000, + port_range_end: 8000, + log_level: "DEBUG", + }); + + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + expect(scriptResource).toBeDefined(); + + // Check that the script contains our custom values + const script = scriptResource?.instances[0].attributes.script as string; + expect(script).toContain('PROJECT_DIR="/workspace/projects"'); + expect(script).toContain('AUTO_START="false"'); + expect(script).toContain('PORT_RANGE_START="4000"'); + expect(script).toContain('PORT_RANGE_END="8000"'); + expect(script).toContain('LOG_LEVEL="DEBUG"'); + }); + + // Test script execution in container + it("should execute script and detect Node.js project", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: true, + }); + + const containerId = await runContainer("ubuntu:22.04"); + + try { + // Install dependencies + await execContainer(containerId, [ + "bash", "-c", + "apt-get update && apt-get install -y jq curl nodejs npm" + ]); + + // Create a test Node.js project + await writeFileContainer(containerId, "/workspace/package.json", JSON.stringify({ + name: "test-project", + scripts: { + start: "echo 'Server started' && sleep 10", + dev: "echo 'Dev server started' && sleep 10" + } + })); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + // Run the script + const result = await execContainer(containerId, ["bash", "-c", script]); + + // Check that the script executed without errors + expect(result.exitCode).toBe(0); + + // Wait a moment for processes to start + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Check if PID file was created + const pidFile = await readFileContainer(containerId, "/root/.auto-dev-server-pids/test-project.pid"); + expect(pidFile).toBeDefined(); + + // Check if log file was created + const logFile = await readFileContainer(containerId, "/root/.auto-dev-server.log"); + expect(logFile).toContain("Found project: test-project"); + expect(logFile).toContain("Starting development server for test-project"); + + } finally { + await removeContainer(containerId); + } + }); + + // Test Python project detection + it("should detect and start Python Flask project", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: true, + }); + + const containerId = await runContainer("python:3.9-slim"); + + try { + // Install dependencies + await execContainer(containerId, [ + "bash", "-c", + "apt-get update && apt-get install -y jq curl" + ]); + + // Create a test Flask project + await writeFileContainer(containerId, "/workspace/app.py", ` +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + return 'Hello, World!' + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + `); + + await writeFileContainer(containerId, "/workspace/requirements.txt", "flask==2.0.1"); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + const result = await execContainer(containerId, ["bash", "-c", script]); + expect(result.exitCode).toBe(0); + + // Wait for processes to start + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Check if Flask process is running + const processes = await execContainer(containerId, ["ps", "aux"]); + expect(processes.stdout).toContain("flask"); + + } finally { + await removeContainer(containerId); + } + }); + + // Test devcontainer integration + it("should use devcontainer postStartCommand", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: true, + }); + + const containerId = await runContainer("ubuntu:22.04"); + + try { + // Install dependencies + await execContainer(containerId, [ + "bash", "-c", + "apt-get update && apt-get install -y jq" + ]); + + // Create devcontainer.json with postStartCommand + await writeFileContainer(containerId, "/workspace/.devcontainer/devcontainer.json", JSON.stringify({ + name: "test-devcontainer", + postStartCommand: "echo 'Custom post-start command executed' && sleep 5" + })); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + const result = await execContainer(containerId, ["bash", "-c", script]); + expect(result.exitCode).toBe(0); + + // Check if the custom command was executed + const logFile = await readFileContainer(containerId, "/root/.auto-dev-server.log"); + expect(logFile).toContain("Custom post-start command executed"); + + } finally { + await removeContainer(containerId); + } + }); + + // Test port allocation + it("should allocate different ports for multiple projects", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: true, + port_range_start: 3000, + port_range_end: 3010, + }); + + const containerId = await runContainer("node:16"); + + try { + // Create multiple Node.js projects + await writeFileContainer(containerId, "/workspace/project1/package.json", JSON.stringify({ + name: "project1", + scripts: { start: "echo 'Project 1 started' && sleep 10" } + })); + + await writeFileContainer(containerId, "/workspace/project2/package.json", JSON.stringify({ + name: "project2", + scripts: { start: "echo 'Project 2 started' && sleep 10" } + })); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + const result = await execContainer(containerId, ["bash", "-c", script]); + expect(result.exitCode).toBe(0); + + // Wait for processes to start + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Check that both projects got different ports + const logFile = await readFileContainer(containerId, "/root/.auto-dev-server.log"); + expect(logFile).toContain("project1"); + expect(logFile).toContain("project2"); + + // Check that different ports were allocated + const portMatches = logFile.match(/Port: (\d+)/g); + expect(portMatches).toBeDefined(); + expect(portMatches!.length).toBeGreaterThan(1); + + // Verify ports are different + const ports = portMatches!.map(match => parseInt(match.split(": ")[1])); + const uniquePorts = new Set(ports); + expect(uniquePorts.size).toBeGreaterThan(1); + + } finally { + await removeContainer(containerId); + } + }); + + // Test cleanup functionality + it("should cleanup dead processes", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: true, + }); + + const containerId = await runContainer("ubuntu:22.04"); + + try { + // Install dependencies + await execContainer(containerId, [ + "bash", "-c", + "apt-get update && apt-get install -y jq" + ]); + + // Create a PID file for a non-existent process + await execContainer(containerId, [ + "bash", "-c", + "mkdir -p /root/.auto-dev-server-pids && echo 99999 > /root/.auto-dev-server-pids/test-project.pid" + ]); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + const result = await execContainer(containerId, ["bash", "-c", script]); + expect(result.exitCode).toBe(0); + + // Check that the dead process PID file was cleaned up + const logFile = await readFileContainer(containerId, "/root/.auto-dev-server.log"); + expect(logFile).toContain("Cleaning up dead process for test-project"); + + // Verify PID file was removed + const pidFileExists = await execContainer(containerId, [ + "test", "-f", "/root/.auto-dev-server-pids/test-project.pid" + ]); + expect(pidFileExists.exitCode).toBe(1); // File should not exist + + } finally { + await removeContainer(containerId); + } + }); + + // Test log level configuration + it("should respect log level configuration", async () => { + const state = await runTerraformApply(moduleDir, { + agent_id: "test-agent-id", + project_dir: "/workspace", + auto_start: false, // Don't start servers, just test logging + log_level: "DEBUG", + }); + + const containerId = await runContainer("ubuntu:22.04"); + + try { + // Install dependencies + await execContainer(containerId, [ + "bash", "-c", + "apt-get update && apt-get install -y jq" + ]); + + // Execute the auto-dev-server script + const scriptResource = state.resources.find( + (r) => r.type === "coder_script" && r.name === "auto_dev_server" + ); + const script = scriptResource?.instances[0].attributes.script as string; + + const result = await execContainer(containerId, ["bash", "-c", script]); + expect(result.exitCode).toBe(0); + + // Check that DEBUG level logging is working + const logFile = await readFileContainer(containerId, "/root/.auto-dev-server.log"); + expect(logFile).toContain("[DEBUG]"); + + } finally { + await removeContainer(containerId); + } + }); +}); diff --git a/registry/kunstewi/modules/auto-dev-server/main.tf b/registry/kunstewi/modules/auto-dev-server/main.tf new file mode 100644 index 00000000..530176d8 --- /dev/null +++ b/registry/kunstewi/modules/auto-dev-server/main.tf @@ -0,0 +1,71 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + } +} + +variable "agent_id" { + description = "The ID of a Coder agent." + type = string +} + +variable "project_dir" { + description = "The directory to scan for projects" + type = string + default = "/home/coder" +} + +variable "auto_start" { + description = "Whether to automatically start development servers" + type = bool + default = true +} + +variable "port_range_start" { + description = "Starting port for development servers" + type = number + default = 3000 +} + +variable "port_range_end" { + description = "Ending port for development servers" + type = number + default = 9000 +} + +variable "log_level" { + description = "Log level for the auto-dev-server script" + type = string + default = "INFO" + validation { + condition = contains(["DEBUG", "INFO", "WARN", "ERROR"], var.log_level) + error_message = "Log level must be one of: DEBUG, INFO, WARN, ERROR" + } +} + +locals { + script_content = templatefile("${path.module}/scripts/auto-dev-server.sh", { + project_dir = var.project_dir + auto_start = var.auto_start + port_range_start = var.port_range_start + port_range_end = var.port_range_end + log_level = var.log_level + }) +} + +resource "coder_script" "auto_dev_server" { + agent_id = var.agent_id + display_name = "Auto Development Server" + icon = "/icon/play.svg" + script = local.script_content + run_on_start = var.auto_start + run_on_stop = false + timeout = 300 +} + +output "script_id" { + description = "The ID of the auto-dev-server script" + value = coder_script.auto_dev_server.id +} \ No newline at end of file diff --git a/registry/kunstewi/modules/auto-dev-server/scripts/auto-dev-server.sh b/registry/kunstewi/modules/auto-dev-server/scripts/auto-dev-server.sh new file mode 100644 index 00000000..c17f15bd --- /dev/null +++ b/registry/kunstewi/modules/auto-dev-server/scripts/auto-dev-server.sh @@ -0,0 +1,289 @@ +#!/bin/bash + +set -euo pipefail + +# Configuration variables +PROJECT_DIR="${project_dir}" +AUTO_START="${auto_start}" +PORT_RANGE_START="${port_range_start}" +PORT_RANGE_END="${port_range_end}" +LOG_LEVEL="${log_level}" +LOG_FILE="$HOME/.auto-dev-server.log" +PID_DIR="$HOME/.auto-dev-server-pids" + +# Logging function +log() { + local level=$1 + shift + local message="$*" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" +} + +# Create PID directory +mkdir -p "$PID_DIR" + +# Project detection functions +detect_nodejs() { + local dir=$1 + if [[ -f "$dir/package.json" ]]; then + local start_script=$(jq -r '.scripts.start // empty' "$dir/package.json" 2>/dev/null || echo "") + local dev_script=$(jq -r '.scripts.dev // empty' "$dir/package.json" 2>/dev/null || echo "") + + if [[ -n "$dev_script" ]]; then + echo "npm run dev" + elif [[ -n "$start_script" ]]; then + echo "npm start" + elif [[ -f "$dir/yarn.lock" ]]; then + echo "yarn start" + else + echo "npm start" + fi + fi +} + +detect_python() { + local dir=$1 + if [[ -f "$dir/requirements.txt" ]] || [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/setup.py" ]]; then + if [[ -f "$dir/manage.py" ]]; then + echo "python manage.py runserver" + elif [[ -f "$dir/app.py" ]] || [[ -f "$dir/main.py" ]]; then + if command -v flask >/dev/null 2>&1; then + echo "flask run" + else + echo "python app.py" + fi + elif [[ -f "$dir/pyproject.toml" ]] && grep -q "fastapi" "$dir/pyproject.toml" 2>/dev/null; then + echo "uvicorn main:app --reload" + fi + fi +} + +detect_ruby() { + local dir=$1 + if [[ -f "$dir/Gemfile" ]]; then + if [[ -f "$dir/config.ru" ]]; then + echo "bundle exec rackup" + elif [[ -f "$dir/config/application.rb" ]]; then + echo "bundle exec rails server" + fi + fi +} + +detect_go() { + local dir=$1 + if [[ -f "$dir/go.mod" ]] || [[ -f "$dir/main.go" ]]; then + echo "go run ." + fi +} + +detect_rust() { + local dir=$1 + if [[ -f "$dir/Cargo.toml" ]]; then + echo "cargo run" + fi +} + +detect_php() { + local dir=$1 + if [[ -f "$dir/composer.json" ]] || [[ -f "$dir/index.php" ]]; then + echo "php -S localhost:8000" + fi +} + +detect_devcontainer() { + local dir=$1 + local devcontainer_file="" + + if [[ -f "$dir/.devcontainer/devcontainer.json" ]]; then + devcontainer_file="$dir/.devcontainer/devcontainer.json" + elif [[ -f "$dir/.devcontainer.json" ]]; then + devcontainer_file="$dir/.devcontainer.json" + fi + + if [[ -n "$devcontainer_file" ]]; then + # Extract postStartCommand from devcontainer.json + local post_start_cmd=$(jq -r '.postStartCommand // empty' "$devcontainer_file" 2>/dev/null || echo "") + if [[ -n "$post_start_cmd" ]]; then + echo "$post_start_cmd" + fi + fi +} + +# Find available port +find_available_port() { + local start_port=$PORT_RANGE_START + local end_port=$PORT_RANGE_END + + for ((port=start_port; port<=end_port; port++)); do + if ! ss -tuln | grep -q ":$port "; then + echo $port + return 0 + fi + done + + log "ERROR" "No available ports in range $start_port-$end_port" + return 1 +} + +# Start development server +start_dev_server() { + local dir=$1 + local command=$2 + local project_name=$(basename "$dir") + local port=$(find_available_port) + + if [[ -z "$port" ]]; then + log "ERROR" "Could not find available port for $project_name" + return 1 + fi + + log "INFO" "Starting development server for $project_name in $dir" + log "INFO" "Command: $command" + log "INFO" "Port: $port" + + cd "$dir" + + # Modify command to use specific port if possible + if [[ "$command" == *"npm"* ]] || [[ "$command" == *"yarn"* ]]; then + command="PORT=$port $command" + elif [[ "$command" == *"flask"* ]]; then + command="$command --port $port" + elif [[ "$command" == *"rails"* ]]; then + command="$command -p $port" + elif [[ "$command" == *"uvicorn"* ]]; then + command="$command --port $port" + fi + + # Start the server in background + nohup bash -c "$command" > "$HOME/.auto-dev-server-$project_name.log" 2>&1 & + local pid=$! + + # Save PID for cleanup + echo $pid > "$PID_DIR/$project_name.pid" + + log "INFO" "Started $project_name with PID $pid on port $port" + + # Create Coder app for the development server + if command -v coder >/dev/null 2>&1; then + coder apps create "$project_name-dev" \ + --url "http://localhost:$port" \ + --icon "/icon/code.svg" \ + --display-name "$project_name Development Server" || true + fi +} + +# Main detection and startup logic +scan_and_start_projects() { + log "INFO" "Scanning for projects in $PROJECT_DIR" + + # Find all potential project directories + find "$PROJECT_DIR" -maxdepth 3 -type f \( \ + -name "package.json" -o \ + -name "requirements.txt" -o \ + -name "pyproject.toml" -o \ + -name "Gemfile" -o \ + -name "go.mod" -o \ + -name "Cargo.toml" -o \ + -name "composer.json" -o \ + -name "devcontainer.json" -o \ + -name ".devcontainer.json" \ + \) | while read -r file; do + local project_dir=$(dirname "$file") + local project_name=$(basename "$project_dir") + + # Skip if already running + if [[ -f "$PID_DIR/$project_name.pid" ]] && kill -0 "$(cat "$PID_DIR/$project_name.pid")" 2>/dev/null; then + log "INFO" "Project $project_name is already running" + continue + fi + + log "INFO" "Found project: $project_name in $project_dir" + + # Try different detection methods + local command="" + + # Check devcontainer first + command=$(detect_devcontainer "$project_dir") + if [[ -z "$command" ]]; then + command=$(detect_nodejs "$project_dir") + fi + if [[ -z "$command" ]]; then + command=$(detect_python "$project_dir") + fi + if [[ -z "$command" ]]; then + command=$(detect_ruby "$project_dir") + fi + if [[ -z "$command" ]]; then + command=$(detect_go "$project_dir") + fi + if [[ -z "$command" ]]; then + command=$(detect_rust "$project_dir") + fi + if [[ -z "$command" ]]; then + command=$(detect_php "$project_dir") + fi + + if [[ -n "$command" ]]; then + start_dev_server "$project_dir" "$command" + else + log "WARN" "No suitable development server command found for $project_name" + fi + done +} + +# Cleanup function +cleanup_dead_processes() { + log "INFO" "Cleaning up dead processes" + + for pid_file in "$PID_DIR"/*.pid; do + if [[ -f "$pid_file" ]]; then + local pid=$(cat "$pid_file") + local project_name=$(basename "$pid_file" .pid) + + if ! kill -0 "$pid" 2>/dev/null; then + log "INFO" "Cleaning up dead process for $project_name (PID: $pid)" + rm -f "$pid_file" + fi + fi + done +} + +# Install required dependencies +install_dependencies() { + log "INFO" "Checking and installing dependencies" + + # Install jq if not available + if ! command -v jq >/dev/null 2>&1; then + log "INFO" "Installing jq" + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y jq + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y jq + elif command -v apk >/dev/null 2>&1; then + sudo apk add jq + fi + fi +} + +# Main execution +main() { + log "INFO" "Auto Development Server starting..." + log "INFO" "Project directory: $PROJECT_DIR" + log "INFO" "Auto start: $AUTO_START" + log "INFO" "Port range: $PORT_RANGE_START-$PORT_RANGE_END" + + install_dependencies + cleanup_dead_processes + + if [[ "$AUTO_START" == "true" ]]; then + scan_and_start_projects + else + log "INFO" "Auto start is disabled" + fi + + log "INFO" "Auto Development Server completed" +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/registry/kunstewi/modules/auto-dev-server/variables.tf b/registry/kunstewi/modules/auto-dev-server/variables.tf new file mode 100644 index 00000000..4b77db9f --- /dev/null +++ b/registry/kunstewi/modules/auto-dev-server/variables.tf @@ -0,0 +1,34 @@ +variable "agent_id" { + description = "The ID of a Coder agent." + type = string +} + +variable "project_dir" { + description = "The directory to scan for projects" + type = string + default = "/home/coder" +} + +variable "auto_start" { + description = "Whether to automatically start development servers" + type = bool + default = true +} + +variable "port_range_start" { + description = "Starting port for development servers" + type = number + default = 3000 +} + +variable "port_range_end" { + description = "Ending port for development servers" + type = number + default = 9000 +} + +variable "log_level" { + description = "Log level for the auto-dev-server script" + type = string + default = "INFO" +} \ No newline at end of file