{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Pitchfork Configuration",
  "description": "Configuration schema for pitchfork.toml daemon supervisor configuration files.\n\nNote: When read from a file, daemon keys are short names (e.g., \"api\").\nAfter merging, keys become qualified DaemonIds (e.g., \"project/api\").",
  "type": "object",
  "properties": {
    "daemons": {
      "description": "Map of daemon IDs to their configurations",
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^[\\w.-]+(/[\\w.-]+)?$": {
          "$ref": "#/$defs/PitchforkTomlDaemon"
        }
      }
    },
    "namespace": {
      "description": "Optional explicit namespace declared in this file.\n\nThis applies to per-file read/write flows. Merged configs may contain\ndaemons from multiple namespaces and leave this as `None`.",
      "type": [
        "string",
        "null"
      ]
    },
    "settings": {
      "description": "Settings configuration (merged from all config files).\n\n**Note:** This field exists for serialization round-trips and for\n`PitchforkToml::merge()` to collect per-file overrides.  It is **not**\nconsumed by the global `settings()` singleton, which is populated\nindependently by `Settings::load()` to avoid a circular dependency\nbetween `PitchforkToml` and `Settings`.  Do not rely on mutations to\nthis field being reflected in `settings()`.",
      "$ref": "#/$defs/SettingsPartial",
      "default": {
        "general": {},
        "ipc": {},
        "proxy": {},
        "supervisor": {},
        "tui": {},
        "web": {}
      }
    }
  },
  "required": [
    "daemons"
  ],
  "$defs": {
    "CpuLimit": {
      "description": "CPU usage limit as a percentage (e.g. 80 for 80% of one core, 200 for 2 cores)",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "CronRetrigger": {
      "description": "Retrigger behavior for cron-scheduled daemons",
      "oneOf": [
        {
          "description": "Retrigger only if the previous run has finished (success or error)",
          "type": "string",
          "const": "finish"
        },
        {
          "description": "Always retrigger, stopping the previous run if still active",
          "type": "string",
          "const": "always"
        },
        {
          "description": "Retrigger only if the previous run succeeded",
          "type": "string",
          "const": "success"
        },
        {
          "description": "Retrigger only if the previous run failed",
          "type": "string",
          "const": "fail"
        }
      ]
    },
    "DaemonId": {
      "description": "Daemon name (e.g. 'api') or qualified ID ('namespace/name') for cross-namespace references",
      "type": "string",
      "pattern": "^[\\w.-]+(/[\\w.-]+)?$"
    },
    "MemoryLimit": {
      "description": "Memory limit in human-readable format, e.g. '50MB', '1GiB', '512KB'",
      "type": "string"
    },
    "PitchforkTomlAuto": {
      "description": "Auto start/stop configuration",
      "type": "string",
      "enum": [
        "start",
        "stop"
      ]
    },
    "PitchforkTomlCron": {
      "description": "Cron scheduling configuration",
      "type": "object",
      "properties": {
        "retrigger": {
          "description": "Behavior when cron triggers while previous run is still active",
          "$ref": "#/$defs/CronRetrigger",
          "default": "finish"
        },
        "schedule": {
          "description": "Cron expression (e.g., '0 * * * *' for hourly, '*/5 * * * *' for every 5 minutes)",
          "type": "string",
          "examples": [
            "0 * * * *"
          ]
        }
      },
      "required": [
        "schedule"
      ]
    },
    "PitchforkTomlDaemon": {
      "description": "Configuration for a single daemon (internal representation with DaemonId)",
      "type": "object",
      "properties": {
        "auto": {
          "description": "Automatic start/stop behavior based on shell hooks",
          "type": "array",
          "default": [],
          "items": {
            "$ref": "#/$defs/PitchforkTomlAuto"
          }
        },
        "auto_bump_port": {
          "description": "Automatically find an available port if the specified port is in use",
          "type": "boolean",
          "default": false
        },
        "boot_start": {
          "description": "Whether to start this daemon automatically on system boot",
          "type": [
            "boolean",
            "null"
          ]
        },
        "cpu_limit": {
          "description": "CPU usage limit as a percentage (e.g. 80 for 80%, 200 for 2 cores).\nThe supervisor periodically monitors CPU usage and kills the process if it exceeds the limit.",
          "anyOf": [
            {
              "$ref": "#/$defs/CpuLimit"
            },
            {
              "type": "null"
            }
          ]
        },
        "cron": {
          "description": "Cron scheduling configuration for periodic execution",
          "anyOf": [
            {
              "$ref": "#/$defs/PitchforkTomlCron"
            },
            {
              "type": "null"
            }
          ]
        },
        "depends": {
          "description": "List of daemon IDs that must be started before this one",
          "type": "array",
          "default": [],
          "items": {
            "$ref": "#/$defs/DaemonId"
          }
        },
        "dir": {
          "description": "Working directory for the daemon. Relative paths are resolved from the pitchfork.toml location.",
          "type": [
            "string",
            "null"
          ]
        },
        "env": {
          "description": "Environment variables to set for the daemon process",
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": {
            "type": "string"
          }
        },
        "expected_port": {
          "description": "TCP ports the daemon is expected to bind to",
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint16",
            "maximum": 65535,
            "minimum": 0
          }
        },
        "hooks": {
          "description": "Lifecycle hooks (on_ready, on_fail, on_retry)",
          "anyOf": [
            {
              "$ref": "#/$defs/PitchforkTomlHooks"
            },
            {
              "type": "null"
            }
          ]
        },
        "memory_limit": {
          "description": "Memory limit for the daemon process (e.g. \"50MB\", \"1GiB\").\nThe supervisor periodically monitors RSS and kills the process if it exceeds the limit.",
          "anyOf": [
            {
              "$ref": "#/$defs/MemoryLimit"
            },
            {
              "type": "null"
            }
          ]
        },
        "mise": {
          "description": "Wrap this daemon's command with `mise x --` for tool/env setup.\nOverrides the global `settings.general.mise` when set.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "port_bump_attempts": {
          "description": "Maximum number of port bump attempts when auto_bump_port is enabled (default: 10)",
          "type": "integer",
          "format": "uint32",
          "default": 10,
          "minimum": 0
        },
        "ready_cmd": {
          "description": "Shell command to poll for readiness (exit code 0 = ready)",
          "type": [
            "string",
            "null"
          ]
        },
        "ready_delay": {
          "description": "Delay in seconds before considering the daemon ready",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "minimum": 0
        },
        "ready_http": {
          "description": "HTTP URL to poll for readiness (expects 2xx response)",
          "type": [
            "string",
            "null"
          ]
        },
        "ready_output": {
          "description": "Regex pattern to match in stdout/stderr to determine readiness",
          "type": [
            "string",
            "null"
          ]
        },
        "ready_port": {
          "description": "TCP port to check for readiness (connection success = ready)",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint16",
          "maximum": 65535,
          "minimum": 1
        },
        "retry": {
          "description": "Number of times to retry if the daemon fails.\nCan be a number (e.g., `3`) or `true` for infinite retries.",
          "$ref": "#/$defs/Retry",
          "default": 0
        },
        "run": {
          "description": "The command to run. Prepend with 'exec' to avoid shell process overhead.",
          "type": "string",
          "examples": [
            "exec node server.js"
          ]
        },
        "user": {
          "description": "Unix user to run this daemon as. Overrides `settings.supervisor.user` when set.",
          "type": [
            "string",
            "null"
          ]
        },
        "watch": {
          "description": "File patterns to watch for changes",
          "type": "array",
          "default": [],
          "items": {
            "type": "string"
          }
        },
        "watch_mode": {
          "description": "File watching backend mode.\n\n- `native`: use platform-native notifications (default)\n- `poll`: use polling-based watcher\n- `auto`: prefer native, fall back to polling if native watch fails",
          "$ref": "#/$defs/WatchMode",
          "default": "native"
        }
      },
      "required": [
        "run"
      ]
    },
    "PitchforkTomlHooks": {
      "description": "Lifecycle hooks for a daemon",
      "type": "object",
      "properties": {
        "on_exit": {
          "description": "Command to run on any daemon termination (clean exit, crash, or stop)",
          "type": [
            "string",
            "null"
          ]
        },
        "on_fail": {
          "description": "Command to run when the daemon fails and all retries are exhausted",
          "type": [
            "string",
            "null"
          ]
        },
        "on_ready": {
          "description": "Command to run when the daemon becomes ready",
          "type": [
            "string",
            "null"
          ]
        },
        "on_retry": {
          "description": "Command to run before each retry attempt",
          "type": [
            "string",
            "null"
          ]
        },
        "on_stop": {
          "description": "Command to run when the daemon is explicitly stopped by pitchfork",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "Retry": {
      "description": "Retry configuration that accepts either a boolean or a count.\n- `true` means retry indefinitely (u32::MAX)\n- `false` or `0` means no retries\n- A number means retry that many times",
      "type": "integer",
      "format": "uint32",
      "minimum": 0
    },
    "SettingsGeneralPartial": {
      "type": "object",
      "properties": {
        "autostop_delay": {
          "description": "Delay before auto-stopping daemons when leaving a directory",
          "type": [
            "string",
            "null"
          ]
        },
        "interval": {
          "description": "Supervisor background task refresh interval",
          "type": [
            "string",
            "null"
          ]
        },
        "log_file_level": {
          "description": "File log level (trace, debug, info, warn, error)",
          "type": [
            "string",
            "null"
          ]
        },
        "log_level": {
          "description": "Console log level (trace, debug, info, warn, error)",
          "type": [
            "string",
            "null"
          ]
        },
        "mise": {
          "description": "Wrap daemon commands with mise x -- globally",
          "type": [
            "boolean",
            "null"
          ]
        },
        "mise_bin": {
          "description": "Explicit path to the mise binary",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "SettingsIpcPartial": {
      "type": "object",
      "properties": {
        "connect_attempts": {
          "description": "Number of connection retry attempts",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "connect_max_delay": {
          "description": "Maximum delay between connection retries",
          "type": [
            "string",
            "null"
          ]
        },
        "connect_min_delay": {
          "description": "Minimum delay between connection retries",
          "type": [
            "string",
            "null"
          ]
        },
        "rate_limit": {
          "description": "Maximum IPC requests per second per connection",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "rate_limit_window": {
          "description": "Rate limit sliding window duration",
          "type": [
            "string",
            "null"
          ]
        },
        "request_timeout": {
          "description": "Default timeout for IPC requests",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "SettingsPartial": {
      "type": "object",
      "properties": {
        "general": {
          "$ref": "#/$defs/SettingsGeneralPartial",
          "default": {}
        },
        "ipc": {
          "$ref": "#/$defs/SettingsIpcPartial",
          "default": {}
        },
        "proxy": {
          "$ref": "#/$defs/SettingsProxyPartial",
          "default": {}
        },
        "supervisor": {
          "$ref": "#/$defs/SettingsSupervisorPartial",
          "default": {}
        },
        "tui": {
          "$ref": "#/$defs/SettingsTuiPartial",
          "default": {}
        },
        "web": {
          "$ref": "#/$defs/SettingsWebPartial",
          "default": {}
        }
      }
    },
    "SettingsProxyPartial": {
      "type": "object",
      "properties": {
        "auto_start": {
          "description": "Automatically start daemons when accessed via proxy URL",
          "type": [
            "boolean",
            "null"
          ]
        },
        "auto_start_timeout": {
          "description": "Maximum time to wait for an auto-started daemon to become ready",
          "type": [
            "string",
            "null"
          ]
        },
        "enable": {
          "description": "Enable the reverse proxy server for daemons",
          "type": [
            "boolean",
            "null"
          ]
        },
        "host": {
          "description": "Bind address for the reverse proxy server",
          "type": [
            "string",
            "null"
          ]
        },
        "https": {
          "description": "Enable HTTPS for the reverse proxy",
          "type": [
            "boolean",
            "null"
          ]
        },
        "port": {
          "description": "Port the reverse proxy server listens on",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "tld": {
          "description": "Top-level domain used for proxy URLs",
          "type": [
            "string",
            "null"
          ]
        },
        "tls_cert": {
          "description": "Path to TLS certificate file (PEM format) for HTTPS proxy",
          "type": [
            "string",
            "null"
          ]
        },
        "tls_key": {
          "description": "Path to TLS private key file (PEM format) for HTTPS proxy",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "SettingsSupervisorPartial": {
      "type": "object",
      "properties": {
        "container": {
          "description": "Enable container/PID1 mode for running inside Docker containers",
          "type": [
            "boolean",
            "null"
          ]
        },
        "cpu_violation_threshold": {
          "description": "Consecutive CPU-over-limit samples before killing a daemon",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "cron_check_interval": {
          "description": "Interval for checking cron schedules",
          "type": [
            "string",
            "null"
          ]
        },
        "file_watch_debounce": {
          "description": "File watch debounce duration",
          "type": [
            "string",
            "null"
          ]
        },
        "http_client_timeout": {
          "description": "Timeout for HTTP ready checks",
          "type": [
            "string",
            "null"
          ]
        },
        "log_flush_interval": {
          "description": "Daemon log buffer flush interval",
          "type": [
            "string",
            "null"
          ]
        },
        "port_bump_attempts": {
          "description": "Maximum port increment attempts when auto_bump_port is enabled",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "ready_check_interval": {
          "description": "Interval between ready checks (HTTP, TCP, command)",
          "type": [
            "string",
            "null"
          ]
        },
        "restart_delay": {
          "description": "Delay between stop and start during restart",
          "type": [
            "string",
            "null"
          ]
        },
        "stop_timeout": {
          "description": "Maximum time to wait for daemon to stop gracefully",
          "type": [
            "string",
            "null"
          ]
        },
        "user": {
          "description": "Default user to run daemon processes as",
          "type": [
            "string",
            "null"
          ]
        },
        "watch_interval": {
          "description": "File watcher config refresh interval",
          "type": [
            "string",
            "null"
          ]
        },
        "watch_poll_interval": {
          "description": "Polling watcher filesystem scan interval",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "SettingsTuiPartial": {
      "type": "object",
      "properties": {
        "message_duration": {
          "description": "Status message display duration",
          "type": [
            "string",
            "null"
          ]
        },
        "refresh_rate": {
          "description": "Daemon list refresh interval",
          "type": [
            "string",
            "null"
          ]
        },
        "stat_history": {
          "description": "Number of stat samples to keep for graphs",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "tick_rate": {
          "description": "Event loop tick rate",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "SettingsWebPartial": {
      "type": "object",
      "properties": {
        "auto_start": {
          "description": "Automatically start web UI when supervisor starts",
          "type": [
            "boolean",
            "null"
          ]
        },
        "base_path": {
          "description": "URL path prefix for the web UI (e.g. \"ps\" serves at /ps/)",
          "type": [
            "string",
            "null"
          ]
        },
        "bind_address": {
          "description": "Web server bind address",
          "type": [
            "string",
            "null"
          ]
        },
        "bind_port": {
          "description": "Default web server port",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "log_lines": {
          "description": "Initial number of log lines to display",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "port_attempts": {
          "description": "Number of ports to try if default is in use",
          "type": [
            "integer",
            "null"
          ],
          "format": "int64"
        },
        "sse_poll_interval": {
          "description": "Server-Sent Events poll interval for log streaming",
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "WatchMode": {
      "description": "File watch backend mode for daemon `watch` patterns.",
      "oneOf": [
        {
          "description": "Use platform-native watcher backend (inotify/FSEvents/ReadDirectoryChangesW).",
          "type": "string",
          "const": "native"
        },
        {
          "description": "Use polling backend; more compatible on networked filesystems.",
          "type": "string",
          "const": "poll"
        },
        {
          "description": "Prefer native backend, fall back to polling when native watch setup fails.",
          "type": "string",
          "const": "auto"
        }
      ]
    }
  }
}
