Skill: laoshu133/dokploy-v2 (version 1.0.1)

--- name: dokploy description: "Manage Dokploy deployments, projects, applications, and domains via the Dokploy API." emoji: "🐳" metadata: clawdhub: requires: bins: ["curl", "jq"] --- # Dokploy Skill Interact with Dokploy's API to manage projects, applications, domains, and deployments. ## Prerequisites 1. **Dokploy instance** running with API access 2. **API Key** generated from `/settings/profile` → "API/CLI Section" 3. Set the `DOKPLOY_API_URL` environment variable (default: `http://localhost:3000`) ## Configuration Set these environment variables or use the config command: ```bash # Dokploy instance URL export DOKPLOY_API_URL="https://your-dokploy-instance.com" # Your API token export DOKPLOY_API_KEY="your-generated-api-key" # Or run the config command dokploy-config set --url "https://your-dokploy-instance.com" --key "your-api-key" ``` ## Projects ### List all projects ```bash dokploy-project list ``` ### Get project details ```bash dokploy-project get <project-id> ``` ### Create a new project ```bash dokploy-project create --name "My Project" --description "Description here" ``` ### Update a project ```bash dokploy-project update <project-id> --name "New Name" --description "Updated" ``` ### Delete a project ```bash dokploy-project delete <project-id> ``` ### List environments in a project ```bash dokploy-project envs <project-id> ``` ## Applications ### List applications in a project ```bash dokploy-app list --project <project-id> ``` ### Get application details ```bash dokploy-app get <application-id> ``` ### Create an application ```bash dokploy-app create \ --environment-id <environment-id> \ --name "my-app" \ --type "docker" \ --image "nginx:latest" ``` **Application types:** `docker`, `git`, `compose` ### Trigger deployment ```bash dokploy-app deploy <application-id> ``` ### Get deployment logs ```bash dokploy-app logs <application-id> --deployment <deployment-id> ``` ### List deployments ```bash dokploy-app deployments <application-id> ``` ### Update application ```bash dokploy-app update <application-id> --name "new-name" --env "KEY=VALUE" ``` ### Delete an application ```bash dokploy-app delete <application-id> ``` ## Domains ### List domains for an application ```bash dokploy-domain list --app <application-id> ``` ### Get domain details ```bash dokploy-domain get <domain-id> ``` ### Add a domain to an application ```bash dokploy-domain create \ --app <application-id> \ --host "app.example.com" \ --path "/" \ --port 80 ``` ### Update a domain ```bash dokploy-domain update <domain-id> --host "new.example.com" ``` ### Delete a domain ```bash dokploy-domain delete <domain-id> ``` ## Environment Variables ### List environment variables for an application ```bash dokploy-app env list <application-id> ``` ### Set environment variable ```bash dokploy-app env set <application-id> --key "DATABASE_URL" --value "postgres://..." ``` ### Delete environment variable ```bash dokploy-app env delete <application-id> --key "DATABASE_URL" ``` ## Utility Commands ### Check API connection ```bash dokploy-status ``` ### View current config ```bash dokploy-config show ``` ## API Reference Base URL: `$DOKPLOY_API_URL/api` | Endpoint | Method | Description | |----------|--------|-------------| | `/project.all` | GET | List all projects | | `/project.create` | POST | Create project | | `/project.one` | GET | Get project by ID | | `/project.update` | POST | Update project | | `/project.remove` | POST | Delete project | | `/application.search` | GET | List applications | | `/application.create` | POST | Create application | | `/application.one` | GET | Get application by ID | | `/application.update` | POST | Update application | | `/application.delete` | POST | Delete application | | `/application.deploy` | POST | Trigger deployment | | `/deployment.all` | GET | List deployments | | `/deployment.byId` | GET | Get deployment by ID | | `/deployment.logs` | GET | Get deployment logs | | `/domain.all` | GET | List domains | | `/domain.create` | POST | Create domain | | `/domain.update` | PATCH | Update domain | | `/domain.delete` | DELETE | Delete domain | ## Notes - All API calls require the `x-api-key` header - Use `jq` for JSON parsing in scripts - Some operations require admin permissions - Deployment is asynchronous — use status endpoint to check progress - **Note**: Currently, Dokploy only provides a REST API for **Deployment Logs**. Real-time **Application Runtime Logs** (container logs) are only available via WebSocket and cannot be accessed through this CLI skill.

path: _meta.json
content: {
  "owner": "joshuarileydev",
  "slug": "dokploy",
  "displayName": "Dokploy",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1768668846190,
    "commit": "https://github.com/clawdbot/skills/commit/dd49cf45db0ba7a35efee15722677931a49e0bb9"
  },
  "history": []
}


path: .clawdhub/package.json
content: {
  "name": "dokploy",
  "version": "1.0.0",
  "description": "Manage Dokploy deployments, projects, applications, and domains via Dokploy API.",
  "keywords": ["dokploy", "deployment", "docker", "devops"],
  "author": "Clawdbot Community",
  "license": "MIT",
  "clawdhub": {
    "slug": "dokploy",
    "category": "devops",
    "requires": {
      "bins": ["curl", "jq"]
    },
    "env": ["DOKPLOY_API_URL", "DOKPLOY_API_KEY"]
  },
  "scripts": {
    "dokploy": "./scripts/dokploy.sh",
    "dokploy-config": "./scripts/dokploy-config.sh",
    "dokploy-project": "./scripts/dokploy-project.sh",
    "dokploy-app": "./scripts/dokploy-app.sh",
    "dokploy-domain": "./scripts/dokploy-domain.sh"
  }
}


path: scripts/dokploy-project.sh
content: #!/bin/bash
# Dokploy Project Management

# Source helper functions
source "$(dirname "$0")/dokploy.sh"

# Colors (redirect log_info to stderr)
log_info() { echo -e "\033[0;34m[INFO]\033[0m $1" >&2; }
log_success() { echo -e "\033[0;32m[SUCCESS]\033[0m $1"; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1" >&2; }
log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1" >&2; }

main() {
case "$1" in
    list)
        log_info "Fetching projects..."
        local response=$(api_request "GET" "/project.all")
        echo "$response" | jq -r '.[] | "ID: \(.projectId)\n  Name: \(.name)\n  Description: \(.description // "N/A")\n  Apps: \(.applications | length)\n"' 2>/dev/null || log_error "Failed to fetch projects"
        ;;
    get)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy project get <project-id>"
            exit 1
        fi
        log_info "Fetching project $2..."
        # Updated: /project.byId -> /project.one
        local response=$(api_request "GET" "/project.one?projectId=$2")
        echo "$response" | jq '.' 2>/dev/null || log_error "Failed to fetch project"
        ;;
    create)
        local name=""
        local description=""

        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --name)
                    name="$2"
                    shift 2
                    ;;
                --description)
                    description="$2"
                    shift 2
                    ;;
                *)
                    log_error "Unknown option: $1"
                    exit 1
                    ;;
            esac
        done

        if [ -z "$name" ]; then
            log_error "Usage: dokploy project create --name <name> [--description <desc>]"
            exit 1
        fi

        local data=$(jq -n \
            --arg name "$name" \
            --arg description "$description" \
            '{name: $name, description: $description}')

        log_info "Creating project '$name'..."
        local response=$(api_request "POST" "/project.create" "$data")
        local projectId=$(echo "$response" | jq -r '.projectId')

        if [ -n "$projectId" ] && [ "$projectId" != "null" ]; then
            log_success "Project created with ID: $projectId"
        else
            log_error "Failed to create project"
            echo "$response" | jq '.'
            exit 1
        fi
        ;;
    update)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy project update <project-id> --name <name> [--description <desc>]"
            exit 1
        fi

        local projectId="$2"
        local name=""
        local description=""

        shift 2
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --name)
                    name="$2"
                    shift 2
                    ;;
                --description)
                    description="$2"
                    shift 2
                    ;;
                *)
                    log_error "Unknown option: $1"
                    exit 1
                    ;;
            esac
        done

        local data=$(jq -n \
            --arg projectId "$projectId" \
            --arg name "$name" \
            --arg description "$description" \
            '{projectId: $projectId}')

        if [ -n "$name" ]; then
            data=$(echo "$data" | jq --arg name "$name" '. + {name: $name}')
        fi

        if [ -n "$description" ]; then
            data=$(echo "$data" | jq --arg description "$description" '. + {description: $description}')
        fi

        log_info "Updating project $projectId..."
        # Updated: PATCH /project.update -> POST /project.update
        local response=$(api_request "POST" "/project.update" "$data")

        if echo "$response" | jq -e '.projectId' > /dev/null 2>&1; then
            log_success "Project updated"
        else
            log_error "Failed to update project"
            echo "$response"
            exit 1
        fi
        ;;
    delete)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy project delete <project-id>"
            exit 1
        fi

        log_warn "Are you sure you want to delete project $2?"
        read -p "Type 'yes' to confirm: " confirm

        if [ "$confirm" != "yes" ]; then
            log_info "Cancelled"
            exit 0
        fi

        log_info "Deleting project $2..."
        # Updated: DELETE /project.delete -> POST /project.remove
        local data=$(jq -n --arg projectId "$2" '{projectId: $projectId}')
        local response=$(api_request "POST" "/project.remove" "$data")

        if echo "$response" | jq -e 'true' > /dev/null 2>&1; then
            log_success "Project deleted"
        else
            if echo "$response" | jq -e '.projectId' > /dev/null 2>&1; then
                 log_success "Project deleted"
            else
                 log_error "Failed to delete project"
                 echo "$response"
                 exit 1
            fi
        fi
        ;;
    envs)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy project envs <project-id>"
            exit 1
        fi
        log_info "Fetching environments for project $2..."
        # Updated: /project.byId -> /project.one
        local response=$(api_request "GET" "/project.one?projectId=$2")
        echo "$response" | jq -r '.environments[] | "ID: \(.environmentId)\n  Name: \(.name)\n  Description: \(.description // "N/A")\n"' 2>/dev/null || log_error "Failed to fetch environments"
        ;;
    *)
        echo "Usage: dokploy project <command>"
        echo ""
        echo "Commands:"
        echo "  list                       List all projects"
        echo "  get <project-id>           Get project details"
        echo "  create --name <name>        Create a new project"
        echo "  update <id> --name <name>   Update project"
        echo "  delete <project-id>          Delete a project"
        echo "  envs <project-id>            List environments in a project"
        ;;
esac
}

# Only run main if script is executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

path: scripts/dokploy-domain.sh
content: #!/bin/bash
# Dokploy Domain Management

# Source helper functions
source "$(dirname "$0")/dokploy.sh"

# Colors (redirect log_info to stderr)
log_info() { echo -e "\033[0;34m[INFO]\033[0m $1" >&2; }
log_success() { echo -e "\033[0;32m[SUCCESS]\033[0m $1"; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1" >&2; }
log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1" >&2; }

main() {
case "$1" in
    list)
        local appId=""

        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --application|--app)
                    appId="$2"
                    shift 2
                    ;;
                *)
                    ;;
            esac
        done

        if [ -z "$appId" ]; then
            log_error "Usage: dokploy domain list --app <app-id>"
            exit 1
        fi

        log_info "Fetching domains for application $appId..."

        # New API uses dedicated endpoints
        local response=$(api_request "GET" "/domain.byApplicationId?applicationId=$appId")

        echo "$response" | jq -r ".[] | \"ID: \(.domainId)\n  Domain: \(.host)\n  App: \(.applicationId)\n  Path: \(.path)\n  Port: \(.port)\n  HTTPS: \(.https)\"" 2>/dev/null || log_error "Failed to fetch domains"
        ;;
    get)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy domain get <domain-id>"
            exit 1
        fi
        log_info "Fetching domain $2..."
        local response=$(api_request "GET" "/domain.one?domainId=$2")
        echo "$response" | jq '.' 2>/dev/null || log_error "Failed to fetch domain"
        ;;
    create)
        local appId=""
        local host=""
        local path="/"
        local port=80
        local https="true"

        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --application|--app)
                    appId="$2"
                    shift 2
                    ;;
                --host|--domain)
                    host="$2"
                    shift 2
                    ;;
                --path)
                    path="$2"
                    shift 2
                    ;;
                --port)
                    port="$2"
                    shift 2
                    ;;
                --no-https)
                    https="false"
                    shift
                    ;;
                *)
                    log_error "Unknown option: $1"
                    exit 1
                    ;;
            esac
        done

        if [ -z "$appId" ] || [ -z "$host" ]; then
            log_error "Usage: dokploy domain create --app <id> --host <name> [--path <path>] [--port <port>] [--no-https]"
            exit 1
        fi

        # New API requires 'host' instead of 'domain'
        local data=$(jq -n \
            --arg applicationId "$appId" \
            --arg host "$host" \
            --arg path "$path" \
            --argjson port "$port" \
            --argjson https "$https" \
            '{
                applicationId: $applicationId,
                host: $host,
                path: $path,
                port: $port,
                https: $https
            }')

        log_info "Creating domain '$host' for application $appId..."
        local response=$(api_request "POST" "/domain.create" "$data")
        local domainId=$(echo "$response" | jq -r '.domainId')

        if [ -n "$domainId" ] && [ "$domainId" != "null" ]; then
            log_success "Domain created with ID: $domainId"
        else
            log_error "Failed to create domain"
            echo "$response" | jq '.' 2>/dev/null || echo "$response"
            exit 1
        fi
        ;;
    update)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy domain update <domain-id> --host <name> [--path <path>] [--port <port>]"
            exit 1
        fi

        local domainId="$2"
        local host=""
        local path=""
        local port=""
        local https=""

        shift 2
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --host|--domain)
                    host="$2"
                    shift 2
                    ;;
                --path)
                    path="$2"
                    shift 2
                    ;;
                --port)
                    port="$2"
                    shift 2
                    ;;
                --https)
                    https="$2"
                    shift 2
                    ;;
                --no-https)
                    https="false"
                    shift
                    ;;
                *)
                    ;;
            esac
        done

        # Build update data, new API uses POST to /domain.update
        local data=$(jq -n --arg domainId "$domainId" '{domainId: $domainId}')

        if [ -n "$host" ]; then
            data=$(echo "$data" | jq --arg host "$host" '. + {host: $host}')
        fi
        if [ -n "$path" ]; then
            data=$(echo "$data" | jq --arg path "$path" '. + {path: $path}')
        fi
        if [ -n "$port" ]; then
            data=$(echo "$data" | jq --argjson port "$port" '. + {port: $port}')
        fi
        if [ -n "$https" ]; then
            data=$(echo "$data" | jq --argjson https "$https" '. + {https: $https}')
        fi

        log_info "Updating domain $domainId..."
        local response=$(api_request "POST" "/domain.update" "$data")

        if echo "$response" | jq -e '.domainId' > /dev/null 2>&1; then
            log_success "Domain updated"
        else
            log_error "Failed to update domain"
            echo "$response"
            exit 1
        fi
        ;;
    delete)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy domain delete <domain-id>"
            exit 1
        fi

        log_warn "Are you sure you want to delete domain $2?"
        read -p "Type 'yes' to confirm: " confirm

        if [ "$confirm" != "yes" ]; then
            log_info "Cancelled"
            exit 0
        fi

        log_info "Deleting domain $2..."
        # New API uses POST to /domain.delete with body
        local data=$(jq -n --arg domainId "$2" '{domainId: $domainId}')
        local response=$(api_request "POST" "/domain.delete" "$data")

        if echo "$response" | jq -e 'true' > /dev/null 2>&1; then
            log_success "Domain deleted"
        else
            if echo "$response" | jq -e '.domainId' > /dev/null 2>&1; then
                 log_success "Domain deleted"
            else
                 log_error "Failed to delete domain"
                 echo "$response"
                 exit 1
            fi
        fi
        ;;
    *)
        echo "Usage: dokploy domain <command>"
        echo ""
        echo "Commands:"
        echo "  list --app <id>            List domains"
        echo "  get <domain-id>             Get domain details"
        echo "  create --app <id> ...       Create domain"
        echo "  update <id> --host <name>   Update domain"
        echo "  delete <domain-id>           Delete domain"
        echo ""
        echo "Create options:"
        echo "  --application <id>   Application ID (required)"
        echo "  --host <name>        Domain name (required)"
        echo "  --path <path>       Path (default: /)"
        echo "  --port <port>       Port (default: 80)"
        echo "  --no-https          Disable HTTPS"
        ;;
esac
}

# Only run main if script is executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

path: scripts/dokploy-config.sh
content: #!/bin/bash
# Dokploy Config Management

CONFIG_FILE="$HOME/.dokployrc"

# Helper functions
log_info() { echo -e "\033[0;34m[INFO]\033[0m $1"; }
log_success() { echo -e "\033[0;32m[SUCCESS]\033[0m $1"; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1" >&2; }

case "$1" in
    set)
        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --url)
                    DOKPLOY_API_URL="$2"
                    shift 2
                    ;;
                --key)
                    DOKPLOY_API_KEY="$2"
                    shift 2
                    ;;
                *)
                    log_error "Unknown option: $1"
                    exit 1
                    ;;
            esac
        done

        # Save to config file
        mkdir -p "$(dirname "$CONFIG_FILE")"
        cat > "$CONFIG_FILE" << EOF
export DOKPLOY_API_URL="${DOKPLOY_API_URL}"
export DOKPLOY_API_KEY="${DOKPLOY_API_KEY}"
EOF
        log_success "Config saved to $CONFIG_FILE"
        log_info "Run: source $CONFIG_FILE to load"
        ;;
    show)
        echo "Dokploy Configuration:"
        echo "  API URL: ${DOKPLOY_API_URL:-not set}"
        echo "  API Key: ${DOKPLOY_API_KEY:+***set***} ${DOKPLOY_API_KEY:-not set}"

        if [ -f "$CONFIG_FILE" ]; then
            echo ""
            echo "Config file: $CONFIG_FILE"
            echo "To load: source $CONFIG_FILE"
        fi
        ;;
    *)
        echo "Usage: dokploy-config <command>"
        echo ""
        echo "Commands:"
        echo "  set --url <url> --key <key>  Set configuration"
        echo "  show                            Show current configuration"
        ;;
esac


path: scripts/dokploy.sh
content: #!/bin/bash
# Dokploy CLI - Main entry point

set -e

# Configuration
DOKPLOY_API_URL="${DOKPLOY_API_URL:-http://localhost:3000}"
DOKPLOY_API_KEY="${DOKPLOY_API_KEY:-}"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Logging functions
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1" >&2
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1" >&2
}

# API request helper
api_request() {
    local method="$1"
    local endpoint="$2"
    local data="$3"
    local url="${DOKPLOY_API_URL}/api${endpoint}"

    # Silent mode - don't log when DOKPLOY_SILENT is set
    if [ -z "$DOKPLOY_SILENT" ]; then
        log_info "API: ${method} ${url}"
    fi

    if [ -z "$DOKPLOY_API_KEY" ]; then
        log_error "DOKPLOY_API_KEY not set. Run: export DOKPLOY_API_KEY='your-key'"
        exit 1
    fi

    if [ -n "$data" ]; then
        curl -s -X "${method}" "${url}" \
            -H "accept: application/json" \
            -H "x-[REDACTED_SECRET]" \
            -H "Content-Type: application/json" \
            -d "${data}"
    else
        curl -s -X "${method}" "${url}" \
            -H "accept: application/json" \
            -H "x-[REDACTED_SECRET]"
    fi
}

# Parse JSON helper (requires jq)
json_get() {
    echo "$1" | jq -r "$2"
}

# Check connection
check_connection() {
    local response=$(api_request "GET" "/project.all" 2>/dev/null)
    local type=$(echo "$response" | jq -r 'type' 2>/dev/null || echo "error")

    if [[ "$type" == "array" ]]; then
        return 0
    else
        return 1
    fi
}

# Main function - only execute when running directly (not when sourced)
main() {
    # Command routing
    case "$1" in
        config)
            shift
            bash "${0%/*}/dokploy-config.sh" "$@"
            ;;
        status)
            if check_connection; then
                log_success "Connected to Dokploy at ${DOKPLOY_API_URL}"
                echo
                echo "Projects:"
                api_request "GET" "/project.all" | jq -r '.[] | "- \(.name) (\(.projectId))"' 2>/dev/null || log_warn "No projects found"
            else
                log_error "Failed to connect to Dokploy at ${DOKPLOY_API_URL}"
                log_error "Check DOKPLOY_API_URL and DOKPLOY_API_KEY"
                exit 1
            fi
            ;;
        project)
            shift
            bash "${0%/*}/dokploy-project.sh" "$@"
            ;;
        app|application)
            shift
            bash "${0%/*}/dokploy-app.sh" "$@"
            ;;
        domain)
            shift
            bash "${0%/*}/dokploy-domain.sh" "$@"
            ;;
        *)
            echo "Dokploy CLI - Manage Dokploy deployments"
            echo ""
            echo "Usage: dokploy <command> [options]"
            echo ""
            echo "Commands:"
            echo "  status       Check API connection"
            echo "  config       Manage configuration"
            echo "  project      Manage projects"
            echo "  app          Manage applications"
            echo "  domain       Manage domains"
            echo ""
            echo "Environment Variables:"
            echo "  DOKPLOY_API_URL  Dokploy instance URL (default: http://localhost:3000)"
            echo "  DOKPLOY_API_KEY   API authentication key"
            echo ""
            echo "Examples:"
            echo "  dokploy status"
            echo "  dokploy project list"
            echo "  dokploy app list --project <project-id>"
            echo "  dokploy domain list --app <app-id>"
            ;;
    esac
}

# Only run main if script is executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi


path: scripts/dokploy-app.sh
content: #!/bin/bash
# Dokploy Application Management

# Source helper functions
source "$(dirname "$0")/dokploy.sh"

# Colors (redirect log_info to stderr)
log_info() { echo -e "\033[0;34m[INFO]\033[0m $1" >&2; }
log_success() { echo -e "\033[0;32m[SUCCESS]\033[0m $1"; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1" >&2; }
log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1" >&2; }

main() {
case "$1" in
    list)
        local projectId=""
        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --project)
                    projectId="$2"
                    shift 2
                    ;;
                *)
                    ;;
            esac
        done

        if [ -z "$projectId" ]; then
            log_error "Usage: dokploy app list --project <project-id>"
            exit 1
        fi

        log_info "Fetching applications for project $projectId..."
        # New API uses application.search
        local response=$(api_request "GET" "/application.search?projectId=$projectId")

        echo "$response" | jq -r '
            (if type == "array" then . else .items // .data // [] end) |
            .[] |
            "ID: \(.applicationId)\n  Name: \(.name)\n  Type: \(.buildType // "N/A")\n  Project: \(.projectId)\n  Status: \(.applicationStatus // "N/A")\n  Environment: \(.environmentId)"
        ' 2>/dev/null || log_error "Failed to fetch applications"
        ;;
    get)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy app get <application-id>"
            exit 1
        fi
        log_info "Fetching application $2..."
        local response=$(api_request "GET" "/application.one?applicationId=$2")
        echo "$response" | jq '.' 2>/dev/null || log_error "Failed to fetch application"
        ;;
    create)
        local environmentId=""
        local name=""
        local type="docker"
        local image=""
        local env=""

        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --environment-id)
                    environmentId="$2"
                    shift 2
                    ;;
                --project)
                    log_warn "--project is deprecated for create, use --environment-id"
                    ;;
                --name)
                    name="$2"
                    shift 2
                    ;;
                --type)
                    type="$2"
                    shift 2
                    ;;
                --image)
                    image="$2"
                    shift 2
                    ;;
                --env)
                    env="$2"
                    shift 2
                    ;;
                *)
                    log_error "Unknown option: $1"
                    exit 1
                    ;;
            esac
        done

        if [ -z "$environmentId" ] || [ -z "$name" ]; then
            log_error "Usage: dokploy app create --environment-id <id> --name <name> [--type <type>] [--image <image>] [--env \"KEY=VALUE\"]"
            exit 1
        fi

        # 1. Create Application
        local createData=$(jq -n \
            --arg environmentId "$environmentId" \
            --arg name "$name" \
            '{
                environmentId: $environmentId,
                name: $name
            }')

        log_info "Creating application '$name'..."
        local response=$(api_request "POST" "/application.create" "$createData")
        local appId=$(echo "$response" | jq -r '.applicationId')

        if [ -z "$appId" ] || [ "$appId" == "null" ]; then
            log_error "Failed to create application"
            echo "$response" | jq '.' 2>/dev/null || echo "$response"
            exit 1
        fi

        log_success "Application created with ID: $appId"

        # 2. Update Application with details (image, type, env)
        local updateData=$(jq -n \
            --arg applicationId "$appId" \
            --arg buildType "$type" \
            --arg dockerImage "$image" \
            --arg env "$env" \
            '{
                applicationId: $applicationId,
                buildType: $buildType,
                dockerImage: (if $dockerImage != "" then $dockerImage else null end),
                env: (if $env != "" then $env else null end)
            }')

        if [ -n "$env" ]; then
            local formattedEnv=$(echo "$env" | tr ';' '\n')
            updateData=$(echo "$updateData" | jq --arg env "$formattedEnv" '.env = $env')
        fi

        log_info "Configuring application..."
        local updateResp=$(api_request "POST" "/application.update" "$updateData")

        if echo "$updateResp" | jq -e '.applicationId' > /dev/null 2>&1; then
             log_success "Application configured"
        else
             log_warn "Application created but configuration failed"
             echo "$updateResp"
        fi
        ;;
    deploy)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy app deploy <application-id>"
            exit 1
        fi
        log_info "Triggering deployment for application $2..."
        # New API uses POST body
        local data=$(jq -n --arg applicationId "$2" '{applicationId: $applicationId}')
        local response=$(api_request "POST" "/application.deploy" "$data")

        if echo "$response" | jq -e '.deploymentId' > /dev/null 2>&1; then
            local deploymentId=$(echo "$response" | jq -r '.deploymentId')
            log_success "Deployment started with ID: $deploymentId"
            log_info "Check status with: dokploy app deployments $2"
        else
             echo "$response"
        fi
        ;;
    deployments)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy app deployments <application-id>"
            exit 1
        fi
        log_info "Fetching deployments for application $2..."
        local response=$(api_request "GET" "/deployment.all?applicationId=$2")
        echo "$response" | jq -r '.[] | "ID: \(.deploymentId)\n  Status: \(.status)\n  Created: \(.createdAt)\n  Description: \(.description // "N/A")\n"' 2>/dev/null || log_error "Failed to fetch deployments"
        ;;
    logs)
        local appId=""
        local deploymentId=""

        shift
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --deployment)
                    deploymentId="$2"
                    shift 2
                    ;;
                *)
                    appId="$1"
                    shift
                    ;;
            esac
        done

        if [ -z "$deploymentId" ]; then
            log_error "Usage: dokploy app logs <app-id> --deployment <deployment-id>"
            exit 1
        fi

        log_info "Fetching logs for deployment $deploymentId..."
        local response=$(api_request "GET" "/deployment.logs?deploymentId=$deploymentId")
        echo "$response" | jq '.' 2>/dev/null || log_error "Failed to fetch logs"
        ;;
    update)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy app update <application-id> [--name <name>] [--env \"KEY=VALUE\"]"
            exit 1
        fi

        local appId="$2"
        local name=""
        local env=""

        shift 2
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --name)
                    name="$2"
                    shift 2
                    ;;
                --env)
                    env="$2"
                    shift 2
                    ;;
                *)
                    ;;
            esac
        done

        local data=$(jq -n --arg applicationId "$appId" '{applicationId: $applicationId}')

        if [ -n "$name" ]; then
            data=$(echo "$data" | jq --arg name "$name" '. + {name: $name}')
        fi

        if [ -n "$env" ]; then
             local formattedEnv=$(echo "$env" | tr ';' '\n')
             data=$(echo "$data" | jq --arg env "$formattedEnv" '. + {env: $env}')
        fi

        log_info "Updating application $appId..."
        local response=$(api_request "POST" "/application.update" "$data")

        if echo "$response" | jq -e '.applicationId' > /dev/null 2>&1; then
            log_success "Application updated"
        else
            log_error "Failed to update application"
            echo "$response"
            exit 1
        fi
        ;;
    delete)
        if [ -z "$2" ]; then
            log_error "Usage: dokploy app delete <application-id>"
            exit 1
        fi

        log_warn "Are you sure you want to delete application $2?"
        read -p "Type 'yes' to confirm: " confirm

        if [ "$confirm" != "yes" ]; then
            log_info "Cancelled"
            exit 0
        fi

        log_info "Deleting application $2..."
        local data=$(jq -n --arg applicationId "$2" '{applicationId: $applicationId}')
        local response=$(api_request "POST" "/application.delete" "$data")

        if echo "$response" | jq -e 'true' > /dev/null 2>&1; then
            log_success "Application deleted"
        else
            if echo "$response" | jq -e '.applicationId' > /dev/null 2>&1; then
                 log_success "Application deleted"
            else
                 log_error "Failed to delete application"
                 echo "$response"
                 exit 1
            fi
        fi
        ;;
    env)
        shift
        case "$1" in
            list)
                if [ -z "$2" ]; then
                    log_error "Usage: dokploy app env list <application-id>"
                    exit 1
                fi
                log_info "Fetching environment variables for $2..."
                local response=$(api_request "GET" "/application.one?applicationId=$2")
                echo "$response" | jq -r '.env // ""'
                ;;
            set)
                shift
                local appId="$1"
                local key=""
                local value=""

                shift
                while [[ $# -gt 0 ]]; do
                    case "$1" in
                        --key)
                            key="$2"
                            shift 2
                            ;;
                        --value)
                            value="$2"
                            shift 2
                            ;;
                        *)
                            ;;
                    esac
                done

                if [ -z "$appId" ] || [ -z "$key" ]; then
                    log_error "Usage: dokploy app env set <app-id> --key <key> --value <value>"
                    exit 1
                fi

                local response=$(api_request "GET" "/application.one?applicationId=$appId")
                local currentEnv=$(echo "$response" | jq -r '.env // ""')

                local newEnv=$(echo "$currentEnv" | jq -R -s -r --arg key "$key" --arg value "$value" '
                    split("\n") |
                    map(select(length > 0)) |
                    map(split("=") | {(.[0]): (.[1:] | join("="))}) |
                    add |
                    if . == null then {} else . end |
                    . + {($key): $value} |
                    to_entries |
                    map("\(.key)=\(.value)") |
                    join("\n")
                ')

                local data=$(jq -n --arg applicationId "$appId" --arg env "$newEnv" '{applicationId: $applicationId, env: $env}')

                log_info "Setting environment variable $key for $appId..."
                local updateResp=$(api_request "POST" "/application.saveEnvironment" "$data")

                if echo "$updateResp" | jq -e '.applicationId' > /dev/null 2>&1; then
                    log_success "Environment variable set"
                else
                    log_error "Failed to set environment variable"
                    echo "$updateResp"
                    exit 1
                fi
                ;;
            delete)
                shift
                local appId="$1"
                local key=""

                shift
                while [[ $# -gt 0 ]]; do
                    case "$1" in
                        --key)
                            key="$2"
                            shift 2
                            ;;
                        *)
                            ;;
                    esac
                done

                if [ -z "$appId" ] || [ -z "$key" ]; then
                    log_error "Usage: dokploy app env delete <app-id> --key <key>"
                    exit 1
                fi

                local response=$(api_request "GET" "/application.one?applicationId=$appId")
                local currentEnv=$(echo "$response" | jq -r '.env // ""')

                local newEnv=$(echo "$currentEnv" | jq -R -s -r --arg key "$key" '
                    split("\n") |
                    map(select(length > 0)) |
                    map(split("=") | {(.[0]): (.[1:] | join("="))}) |
                    add |
                    if . == null then {} else . end |
                    del(.[$key]) |
                    to_entries |
                    map("\(.key)=\(.value)") |
                    join("\n")
                ')

                local data=$(jq -n --arg applicationId "$appId" --arg env "$newEnv" '{applicationId: $applicationId, env: $env}')

                log_info "Deleting environment variable $key from $appId..."
                local updateResp=$(api_request "POST" "/application.saveEnvironment" "$data")

                if echo "$updateResp" | jq -e '.applicationId' > /dev/null 2>&1; then
                    log_success "Environment variable deleted"
                else
                    log_error "Failed to delete environment variable"
                    echo "$updateResp"
                    exit 1
                fi
                ;;
            *)
                echo "Usage: dokploy app env <command>"
                echo ""
                echo "Commands:"
                echo "  list <app-id>              List environment variables"
                echo "  set <app-id> --key <k> --value <v>  Set environment variable"
                echo "  delete <app-id> --key <k>   Delete environment variable"
                ;;
        esac
        ;;
    *)
        echo "Usage: dokploy app <command>"
        echo ""
        echo "Commands:"
        echo "  list [--project <id>]      List applications"
        echo "  get <app-id>              Get application details"
        echo "  create --environment-id <id> ...   Create application"
        echo "  deploy <app-id>            Trigger deployment"
        echo "  deployments <app-id>       List deployments"
        echo "  logs <app-id> --deployment <id>  Get deployment logs"
        echo "  update <app-id> ...         Update application"
        echo "  delete <app-id>            Delete application"
        echo "  env                        Environment variable commands"
        ;;
esac
}

# Only run main if script is executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)