{
    "$schema": "http://json-schema.org/draft-07/schema",
    "additionalProperties": false,
    "description": "Appium configuration schema for the Xenon plugin.",
    "properties": {
        "platform": {
            "type": "string",
            "enum": [
                "ios",
                "android",
                "both"
            ],
            "default": "both",
            "description": "Which mobile platform(s) Xenon should discover and orchestrate."
        },
        "androidDeviceType": {
            "title": "DeviceTypeToInclude",
            "type": "string",
            "enum": [
                "both",
                "real",
                "simulated"
            ],
            "default": "both",
            "description": "Which Android device kinds to include: physical devices, emulators, or both."
        },
        "simulators": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/SimulatorConfig"
            },
            "default": [],
            "description": "Allow-list of iOS simulators (by name + sdk) to expose. Empty array means expose all discoverable simulators."
        },
        "iosDeviceType": {
            "title": "DeviceTypeToInclude1",
            "type": "string",
            "enum": [
                "both",
                "real",
                "simulated"
            ],
            "default": "both",
            "description": "Which iOS device kinds to include: physical devices, simulators, or both."
        },
        "hub": {
            "type": "string",
            "description": "URL of the Xenon hub this instance should register with as a node (e.g. http://hub.example:4723). Omit to run as a standalone hub."
        },
        "remoteMachineProxyIP": {
            "type": "string",
            "description": "Public host/URL that clients should use to reach this node when running behind a reverse proxy or NAT."
        },
        "adbRemote": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "default": [],
            "description": "List of remote ADB hosts in host:port form (e.g. '192.168.1.50:5037') to discover Android devices on other machines."
        },
        "skipChromeDownload": {
            "type": "boolean",
            "default": true,
            "description": "Skip the automatic ChromeDriver download performed by uiautomator2. Leave true unless you specifically need Xenon to manage Chrome binaries."
        },
        "maxSessions": {
            "type": "number",
            "default": 8,
            "description": "Maximum number of Appium sessions this node will run concurrently. Additional requests queue until a slot frees."
        },
        "cloud": {
            "type": "object",
            "description": "Cloud configuration object. See CloudConfig interface for details."
        },
        "derivedDataPath": {
            "title": "IDerivedDataPath",
            "type": "object",
            "additionalProperties": {
                "type": "string"
            },
            "description": "Map of derived data paths for iOS simulators/devices."
        },
        "emulators": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/EmulatorConfig"
            },
            "default": [],
            "description": "Allow-list of Android emulator AVDs to expose. Empty array means expose all discoverable emulators."
        },
        "proxy": {
            "type": "object",
            "description": "Proxy configuration object. See AxiosProxy interface for details."
        },
        "deviceAvailabilityTimeoutMs": {
            "type": "number",
            "default": 300000,
            "description": "How long (ms) a session request waits for a free device before failing."
        },
        "deviceAvailabilityQueryIntervalMs": {
            "type": "number",
            "default": 1000,
            "description": "How often (ms) the session queue polls for a free device while waiting. Runs only while a create is actively waiting for a busy/unsynced device, so a tighter interval cuts the wait without steady-state cost."
        },
        "sendNodeDevicesToHubIntervalMs": {
            "type": "number",
            "default": 30000,
            "description": "How often (ms) a node pushes its current device list to the hub. Only used when `hub` is set."
        },
        "checkStaleDevicesIntervalMs": {
            "type": "number",
            "default": 30000,
            "description": "How often (ms) the hub prunes devices from nodes that have stopped heartbeating."
        },
        "checkBlockedDevicesIntervalMs": {
            "type": "number",
            "default": 30000,
            "description": "How often (ms) to re-evaluate manually-blocked devices and the session reconciler that frees orphaned busy devices."
        },
        "newCommandTimeoutSec": {
            "type": "number",
            "default": 60,
            "description": "Default Appium newCommandTimeout (seconds) applied when a client does not send one. Also drives the reconciler that releases devices idle past this threshold."
        },
        "bindHostOrIp": {
            "type": "string",
            "default": "auto",
            "description": "Host/IP advertised in device URLs and the dashboard. Use auto (default), 0.0.0.0, 127.0.0.1, or localhost to pick the best LAN-reachable IPv4 (e.g. 192.168.x.x). Set an explicit address to override."
        },
        "enableDashboard": {
            "type": "boolean",
            "default": false,
            "description": "Serve the React dashboard at /xenon/ and the Socket.io event stream."
        },
        "bootedSimulators": {
            "type": "boolean",
            "default": false,
            "description": "Only discover iOS simulators that are already booted. Recommended on machines with many installed simulators — avoids allocating WDA/MJPEG ports for shutdown sims (the WDA pool is 8100-8199, 100 ports)."
        },
        "bootedEmulators": {
            "type": "boolean",
            "default": false,
            "description": "Only discover Android emulators that are already booted."
        },
        "removeDevicesFromDatabaseBeforeRunningThePlugin": {
            "type": "boolean",
            "default": false,
            "description": "Wipe the persisted Device table at startup so discovery begins from a clean slate. Useful after hardware changes."
        },
        "healthCheckIntervalMs": {
            "type": "number",
            "default": 300000,
            "description": "Default interval (ms) between background device health checks. Overridden when `healthCheckSchedule` is set. Default 5 minutes — frequent enough to keep battery/thermal badges fresh without hammering devices."
        },
        "healthCheckSchedule": {
            "type": "string",
            "description": "Cron expression for the device health-check job (e.g. '0 * * * *' for hourly). When set, takes precedence over `healthCheckIntervalMs`."
        },
        "databaseProvider": {
            "type": "string",
            "enum": [
                "sqlite",
                "postgresql"
            ],
            "description": "Database backend. Defaults to sqlite (file under ~/.cache/xenon). Use postgresql for multi-node hub deployments."
        },
        "databaseUrl": {
            "type": "string",
            "description": "Prisma-style database URL. For sqlite: `file:/path/to/xenon.db`. For postgres: `postgresql://user:pass@host/db`. Falls back to the DATABASE_URL env var."
        },
        "aiProvider": {
            "type": "string",
            "enum": [
                "gemini",
                "openai",
                "anthropic",
                "ollama"
            ],
            "description": "AI provider for the LLM healing tier and visual analysis. Also controlled by XENON_AI_PROVIDER."
        },
        "aiModel": {
            "type": "string",
            "description": "Override the default model for the selected `aiProvider` (e.g. 'gemini-1.5-pro', 'gpt-4o', 'claude-sonnet-4-6'). Falls back to XENON_AI_MODEL."
        },
        "aiBaseUrl": {
            "type": "string",
            "description": "Custom base URL for the AI provider (e.g. a local Ollama server or an OpenAI-compatible gateway). Falls back to XENON_AI_BASE_URL."
        },
        "geminiApiKey": {
            "type": "string",
            "description": "Gemini API key. Prefer setting XENON_GEMINI_API_KEY (or GEMINI_API_KEY) via environment instead of committing it to a config file."
        },
        "openaiApiKey": {
            "type": "string",
            "description": "OpenAI API key. Prefer setting XENON_OPENAI_API_KEY (or OPENAI_API_KEY) via environment."
        },
        "anthropicApiKey": {
            "type": "string",
            "description": "Anthropic API key. Prefer setting XENON_ANTHROPIC_API_KEY (or ANTHROPIC_API_KEY) via environment."
        },
        "enableSelfHealing": {
            "type": "boolean",
            "default": true,
            "description": "Enable the 5-tier self-healing pipeline (Native → Fuzzy XML → OCR → Visual AI → LLM) for failed findElement calls. Can also be toggled at runtime from the dashboard."
        },
        "buildCleanupDays": {
            "type": "number",
            "default": 30,
            "description": "Builds/sessions older than this many days are purged by the cleanup job."
        },
        "buildCleanupMaxCount": {
            "type": "number",
            "default": 100,
            "description": "Maximum number of builds to retain. Oldest-first eviction beyond this cap regardless of `buildCleanupDays`."
        },
        "buildCleanupSchedule": {
            "type": "string",
            "default": "0 0 * * *",
            "description": "Cron expression for the retention job. Default '0 0 * * *' runs at midnight."
        },
        "deleteBuildAssets": {
            "type": "boolean",
            "default": true,
            "description": "When true, the cleanup job also deletes session video recordings and screenshots from disk (not just DB rows)."
        },
        "recordingCleanupDays": {
            "type": "number",
            "default": 30,
            "description": "Live Devices recordings older than this many days are purged (DB row + files) by the cleanup job. Runs on `buildCleanupSchedule`."
        },
        "recordingCleanupMaxCount": {
            "type": "number",
            "default": 100,
            "description": "Maximum number of recordings to retain. Oldest-first eviction beyond this cap regardless of `recordingCleanupDays`."
        },
        "recordingFailedCleanupDays": {
            "type": "number",
            "default": 2,
            "description": "Failed recordings hold no playable file, so they are purged on this much shorter window than `recordingCleanupDays`."
        },
        "sessionHeartbeatIntervalMs": {
            "type": "number",
            "default": 30000,
            "description": "How often (ms) each active session writes a heartbeat. The orphan sweeper uses ~3× this interval to detect abandoned sessions."
        },
        "enableJsonLogging": {
            "type": "boolean",
            "default": false,
            "description": "Emit structured JSON log lines instead of human-readable text. Recommended for shipping logs to a log aggregator."
        },
        "tlsRejectUnauthorized": {
            "type": "boolean",
            "default": true,
            "description": "Whether to verify TLS certificates for internal outgoing requests. Default is true. Set to false only for dev/test."
        },
        "authDisabled": {
            "type": "boolean",
            "default": false,
            "description": "Disable API key authentication for all /xenon/api/* endpoints. Use only in local dev environments."
        },
        "interceptor": {
            "type": "object",
            "description": "Network request interception configuration (capture + mock + modify). See InterceptorConfig interface for details. Android-only in v1."
        },
        "maxConcurrentRecordings": {
            "type": "integer",
            "minimum": 1,
            "maximum": 16,
            "default": 4,
            "description": "Server-wide hard cap on simultaneous free-form (non-session) screen recordings across all users. Automation session recording is exempt and not counted against this cap."
        },
        "recordingsAssetsPath": {
            "type": "string",
            "description": "Override directory for free-form recording artifacts. Defaults to <sessionAssetsPath>/recordings."
        },
        "autowait": {
            "type": "object",
            "description": "Implicit-wait behavior for findElement/findElements and pre-action enabled checks. See AutowaitConfig interface for details."
        },
        "streaming": {
            "type": "object",
            "description": "Live-streaming options. See StreamingConfig interface for details."
        }
    },
    "required": [
        "platform",
        "androidDeviceType",
        "iosDeviceType",
        "skipChromeDownload",
        "maxSessions",
        "deviceAvailabilityTimeoutMs",
        "deviceAvailabilityQueryIntervalMs",
        "sendNodeDevicesToHubIntervalMs",
        "checkStaleDevicesIntervalMs",
        "checkBlockedDevicesIntervalMs",
        "newCommandTimeoutSec",
        "bindHostOrIp",
        "enableDashboard",
        "bootedSimulators",
        "removeDevicesFromDatabaseBeforeRunningThePlugin",
        "healthCheckIntervalMs",
        "enableSelfHealing",
        "buildCleanupDays",
        "buildCleanupMaxCount",
        "buildCleanupSchedule",
        "deleteBuildAssets",
        "sessionHeartbeatIntervalMs",
        "enableJsonLogging"
    ],
    "title": "IPluginArgs",
    "type": "object",
    "definitions": {
        "AxiosProxy": {
            "type": "object",
            "title": "AxiosProxy",
            "properties": {
                "host": {
                    "type": "string"
                },
                "port": {
                    "type": "integer"
                },
                "protocol": {
                    "type": "string",
                    "enum": [
                        "http",
                        "https"
                    ]
                },
                "auth": {
                    "type": "object"
                }
            },
            "additionalProperties": true,
            "description": "Proxy configuration object. Contains 'host', 'port', 'auth', 'protocol'."
        },
        "CloudConfig": {
            "type": "object",
            "title": "CloudConfig",
            "additionalProperties": false,
            "description": "Cloud configuration object. Should be provided via config file.",
            "properties": {
                "cloudName": {
                    "type": "string",
                    "description": "Name of the cloud provider (e.g. browserstack, saucelabs, pcloudy, lambdatest)"
                },
                "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the cloud provider hub"
                },
                "apiKey": {
                    "type": "string",
                    "description": "API Key for the cloud provider"
                },
                "apiUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "API URL for the cloud provider"
                },
                "devices": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/CloudDevice"
                    }
                }
            },
            "required": [
                "cloudName",
                "url",
                "apiKey",
                "devices"
            ]
        },
        "CloudDevice": {
            "type": "object",
            "title": "CloudDevice",
            "additionalProperties": true,
            "description": "Device configuration object",
            "properties": {
                "deviceName": {
                    "type": "string"
                },
                "platform": {
                    "type": "string"
                },
                "os_version": {
                    "type": "string"
                },
                "platformVersion": {
                    "type": "string"
                },
                "pCloudy_DeviceManufacturer": {
                    "type": "string"
                },
                "pCloudy_DeviceVersion": {
                    "type": "string"
                }
            }
        },
        "SimulatorConfig": {
            "type": "object",
            "title": "SimulatorConfig",
            "properties": {
                "name": {
                    "type": "string"
                },
                "sdk": {
                    "type": "string"
                }
            },
            "required": [
                "name",
                "sdk"
            ],
            "additionalProperties": true
        },
        "EmulatorConfig": {
            "type": "object",
            "title": "EmulatorConfig",
            "properties": {
                "avdName": {
                    "type": "string"
                }
            },
            "required": [
                "avdName"
            ],
            "additionalProperties": true
        },
        "AutowaitConfig": {
            "type": "object",
            "title": "AutowaitConfig",
            "additionalProperties": false,
            "description": "Implicit-wait configuration for findElement/findElements and pre-action enabled checks.",
            "properties": {
                "enabled": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable autowait globally. Sessions can override via the `xenon: setAutowaitProperties` execute script."
                },
                "timeoutMs": {
                    "type": "number",
                    "default": 10000,
                    "description": "How long (ms) to keep retrying findElement before throwing NoSuchElement. Self-healing still runs once this timeout expires."
                },
                "intervalBetweenAttemptsMs": {
                    "type": "number",
                    "default": 500,
                    "description": "Sleep (ms) between findElement retries while waiting for the element to appear."
                },
                "excludeEnabledCheck": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    },
                    "default": [],
                    "description": "Action commands (click, setValue, clear) for which the pre-action elementEnabled check should be skipped."
                }
            }
        },
        "InterceptorConfig": {
            "type": "object",
            "title": "InterceptorConfig",
            "additionalProperties": false,
            "description": "Network request interceptor configuration.",
            "properties": {
                "enabled": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable the network interceptor. Sessions still need to opt in via any of: xenon:options.interceptor.enabled=true, xe:interceptor.enabled=true, appium:interceptor.enabled=true, a bare interceptor.enabled=true cap, or the flat interceptorEnabled cap."
                },
                "bufferSize": {
                    "type": "number",
                    "default": 1000,
                    "description": "Maximum number of captured requests to retain in-memory per session before evicting oldest."
                },
                "captureBodies": {
                    "type": "boolean",
                    "default": true,
                    "description": "Whether to capture request/response bodies. Disable for privacy or to reduce memory usage."
                }
            }
        },
        "StreamingConfig": {
            "type": "object",
            "title": "StreamingConfig",
            "additionalProperties": false,
            "description": "Live-streaming options.",
            "properties": {
                "androidH264": {
                    "default": false,
                    "description": "Android live preview: false/omitted = MJPEG; true = H.264 via scrcpy; { \"source\": \"scrcpy\" | \"screenrecord\" } to pick the capture source explicitly. Falls back to MJPEG when unsupported.",
                    "oneOf": [
                        { "type": "boolean" },
                        {
                            "type": "object",
                            "additionalProperties": false,
                            "properties": { "source": { "type": "string", "enum": ["scrcpy", "screenrecord"] } }
                        }
                    ]
                }
            }
        }
    }
}