feat(cli): add Daytona to setup wizard, doctor, and status display

Add Daytona as a backend choice in the interactive setup wizard with
SDK installation and API key prompts. Show Daytona image in status
output and validate API key + SDK in doctor checks. Add OPTION 6
example in cli-config.yaml.example.

Signed-off-by: rovle <lovre.pesut@gmail.com>
This commit is contained in:
rovle
2026-03-05 00:44:39 -08:00
parent 690b8bb563
commit df61054a84
4 changed files with 91 additions and 10 deletions

View File

@@ -116,8 +116,22 @@ terminal:
# timeout: 180
# lifetime_seconds: 300
# modal_image: "nikolaik/python-nodejs:python3.11-nodejs20"
# -----------------------------------------------------------------------------
# OPTION 6: Daytona cloud execution
# Commands run in Daytona cloud sandboxes
# Great for: Cloud dev environments, persistent workspaces, team collaboration
# Requires: pip install daytona, DAYTONA_API_KEY env var
# -----------------------------------------------------------------------------
# terminal:
# backend: "daytona"
# cwd: "/home/daytona"
# timeout: 180
# lifetime_seconds: 300
# daytona_image: "nikolaik/python-nodejs:python3.11-nodejs20"
#
# --- Container resource limits (docker, singularity, modal -- ignored for local/ssh) ---
# --- Container resource limits (docker, singularity, modal, daytona -- ignored for local/ssh) ---
# These settings apply to all container backends. They control the resources
# allocated to the sandbox and whether its filesystem persists across sessions.
container_cpu: 1 # CPU cores

View File

@@ -355,6 +355,21 @@ def run_doctor(args):
check_fail("TERMINAL_SSH_HOST not set", "(required for TERMINAL_ENV=ssh)")
issues.append("Set TERMINAL_SSH_HOST in .env")
# Daytona (if using daytona backend)
if terminal_env == "daytona":
daytona_key = os.getenv("DAYTONA_API_KEY")
if daytona_key:
check_ok("Daytona API key", "(configured)")
else:
check_fail("DAYTONA_API_KEY not set", "(required for TERMINAL_ENV=daytona)")
issues.append("Set DAYTONA_API_KEY environment variable")
try:
from daytona import Daytona
check_ok("daytona SDK", "(installed)")
except ImportError:
check_fail("daytona SDK not installed", "(pip install daytona)")
issues.append("Install daytona SDK: pip install daytona")
# Node.js + agent-browser (for browser automation tools)
if shutil.which("node"):
check_ok("Node.js")

View File

@@ -980,19 +980,20 @@ def run_setup_wizard(args):
terminal_choices.extend([
"Modal (cloud execution, GPU access, serverless)",
"Daytona (cloud sandboxes, persistent workspaces)",
"SSH (run commands on a remote server)",
f"Keep current ({current_backend})"
])
# Build index map based on available choices
if is_linux:
backend_to_idx = {'local': 0, 'docker': 1, 'singularity': 2, 'modal': 3, 'ssh': 4}
idx_to_backend = {0: 'local', 1: 'docker', 2: 'singularity', 3: 'modal', 4: 'ssh'}
keep_current_idx = 5
backend_to_idx = {'local': 0, 'docker': 1, 'singularity': 2, 'modal': 3, 'daytona': 4, 'ssh': 5}
idx_to_backend = {0: 'local', 1: 'docker', 2: 'singularity', 3: 'modal', 4: 'daytona', 5: 'ssh'}
keep_current_idx = 6
else:
backend_to_idx = {'local': 0, 'docker': 1, 'modal': 2, 'ssh': 3}
idx_to_backend = {0: 'local', 1: 'docker', 2: 'modal', 3: 'ssh'}
keep_current_idx = 4
backend_to_idx = {'local': 0, 'docker': 1, 'modal': 2, 'daytona': 3, 'ssh': 4}
idx_to_backend = {0: 'local', 1: 'docker', 2: 'modal', 3: 'daytona', 4: 'ssh'}
keep_current_idx = 5
if current_backend == 'singularity':
print_warning("Singularity is only available on Linux - please select a different backend")
@@ -1067,7 +1068,7 @@ def run_setup_wizard(args):
print()
print_info("Note: Container resource settings (CPU, memory, disk, persistence)")
print_info("are in your config but only apply to Docker/Singularity/Modal backends.")
print_info("are in your config but only apply to Docker/Singularity/Modal/Daytona backends.")
if prompt_yes_no(" Enable sudo support? (allows agent to run sudo commands)", False):
print_warning(" SECURITY WARNING: Sudo password will be stored in plaintext")
@@ -1151,7 +1152,52 @@ def run_setup_wizard(args):
_prompt_container_resources(config)
print_success("Terminal set to Modal")
elif selected_backend == 'daytona':
config.setdefault('terminal', {})['backend'] = 'daytona'
default_daytona = config.get('terminal', {}).get('daytona_image', 'nikolaik/python-nodejs:python3.11-nodejs20')
print_info("Daytona Cloud Configuration:")
print_info("Get your API key at: https://app.daytona.io/dashboard/keys")
# Check if daytona SDK is installed
try:
from daytona import Daytona
print_info("daytona SDK: installed ✓")
except ImportError:
print_info("Installing required package: daytona...")
import subprocess
import shutil
uv_bin = shutil.which("uv")
if uv_bin:
result = subprocess.run(
[uv_bin, "pip", "install", "daytona"],
capture_output=True, text=True
)
else:
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "daytona"],
capture_output=True, text=True
)
if result.returncode == 0:
print_success("daytona SDK installed")
else:
print_warning("Failed to install daytona SDK — install manually:")
print_info(' pip install daytona')
daytona_image = prompt(" Container image", default_daytona)
config['terminal']['daytona_image'] = daytona_image
current_key = get_env_value('DAYTONA_API_KEY')
if current_key:
print_info(f" API Key: {current_key[:8]}... (configured)")
api_key = prompt(" Daytona API key", current_key or "", password=True)
if api_key:
save_env_value("DAYTONA_API_KEY", api_key)
_prompt_container_resources(config)
print_success("Terminal set to Daytona")
elif selected_backend == 'ssh':
config.setdefault('terminal', {})['backend'] = 'ssh'
print_info("SSH Remote Execution Configuration:")
@@ -1181,7 +1227,7 @@ def run_setup_wizard(args):
print()
print_info("Note: Container resource settings (CPU, memory, disk, persistence)")
print_info("are in your config but only apply to Docker/Singularity/Modal backends.")
print_info("are in your config but only apply to Docker/Singularity/Modal/Daytona backends.")
print_success("Terminal set to SSH")
# else: Keep current (selected_backend is None)
@@ -1192,6 +1238,9 @@ def run_setup_wizard(args):
docker_image = config.get('terminal', {}).get('docker_image')
if docker_image:
save_env_value("TERMINAL_DOCKER_IMAGE", docker_image)
daytona_image = config.get('terminal', {}).get('daytona_image')
if daytona_image:
save_env_value("TERMINAL_DAYTONA_IMAGE", daytona_image)
# =========================================================================
# Step 5: Agent Settings

View File

@@ -163,6 +163,9 @@ def show_status(args):
elif terminal_env == "docker":
docker_image = os.getenv("TERMINAL_DOCKER_IMAGE", "python:3.11-slim")
print(f" Docker Image: {docker_image}")
elif terminal_env == "daytona":
daytona_image = os.getenv("TERMINAL_DAYTONA_IMAGE", "nikolaik/python-nodejs:python3.11-nodejs20")
print(f" Daytona Image: {daytona_image}")
sudo_password = os.getenv("SUDO_PASSWORD", "")
print(f" Sudo: {check_mark(bool(sudo_password))} {'enabled' if sudo_password else 'disabled'}")