{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/gszj2018/pi-permission-bash-cmd-checker/raw/refs/heads/release/v0.2.0/schemas/bash-cmd-checker.schema.json",
  "title": "Bash Command Checker Configuration",
  "description": "Reload Pi after changing this file. Invalid configuration disables the checker, not the permission system.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Optional schema location for editor validation."
    },
    "llm": {
      "type": "object",
      "additionalProperties": false,
      "default": { "model": null, "language": "en", "timeoutMs": 30000 },
      "properties": {
        "model": {
          "anyOf": [{ "$ref": "#/$defs/model" }, { "type": "null" }],
          "default": null,
          "description": "Null selects the current session model at request time. Explicit models do not fall back."
        },
        "language": {
          "type": "string",
          "enum": ["en", "zh"],
          "default": "en",
          "description": "LLM explanation language: en for English, zh for Simplified Chinese. Does not affect classification or UI labels."
        },
        "timeoutMs": {
          "$ref": "#/$defs/timeout",
          "default": 30000,
          "description": "Background explanation deadline in milliseconds."
        }
      }
    },
    "classifier": {
      "type": "object",
      "additionalProperties": false,
      "default": {
        "model": { "provider": "typesafe", "id": "jev-latest" },
        "timeoutMs": 10000,
        "thresholds": { "safe": 0.5, "unsafe": 0.3, "confidence": 0.8 }
      },
      "properties": {
        "model": {
          "anyOf": [{ "$ref": "#/$defs/model" }, { "type": "null" }],
          "default": { "provider": "typesafe", "id": "jev-latest" },
          "description": "Null disables classification. No automatic provider fallback is used."
        },
        "timeoutMs": {
          "$ref": "#/$defs/timeout",
          "default": 10000,
          "description": "Total classification deadline, including model availability checks, in milliseconds."
        },
        "thresholds": {
          "type": "object",
          "additionalProperties": false,
          "default": { "safe": 0.5, "unsafe": 0.3, "confidence": 0.8 },
          "properties": {
            "safe": {
              "$ref": "#/$defs/probability",
              "default": 0.5,
              "description": "Minimum probability for safe-ro or safe-rw, also requiring the confidence threshold."
            },
            "unsafe": {
              "$ref": "#/$defs/probability",
              "default": 0.3,
              "description": "Minimum unsafe probability, also requiring the confidence threshold. Unsafe takes priority."
            },
            "confidence": {
              "$ref": "#/$defs/probability",
              "default": 0.8,
              "description": "Minimum confidence required to accept any of the three classifier labels."
            }
          }
        }
      }
    },
    "widget": {
      "type": "object",
      "additionalProperties": false,
      "default": { "commandViewerShortcut": "alt+c" },
      "properties": {
        "commandViewerShortcut": {
          "type": "string",
          "default": "alt+c",
          "pattern": "^(?!.*\\b(ctrl|alt|shift|super)\\+.*\\b\\1\\+)(?:(?:ctrl|alt|shift|super)\\+){1,4}(?:[a-z0-9\\x60=\\[\\]\\\\;'.,/!@#$%^&*()_|~{}:<>?\\-]|enter|return|tab|space|backspace|delete|insert|home|end|pageUp|pageDown|up|down|left|right)(?![\\s\\S])",
          "description": "Open the full command in an external editor when the widget is visible. At least one modifier (ctrl, alt, shift or super) is required; unmodified keys are not accepted. Escape/esc, clear and function keys (f1-f12) cannot be configured, even with modifiers; no viewer close keys are registered. Use shift+= rather than + as a base key. Reload Pi to apply changes. Alt+c requires a terminal that reports the combination distinctly."
        }
      }
    },
    "externalViewer": {
      "type": "object",
      "additionalProperties": false,
      "default": { "command": "code", "args": [], "mode": "detach", "filePath": null },
      "properties": {
        "command": {
          "anyOf": [
            { "type": "string", "pattern": "^(?![\\s\\S]*\\u0000)(?=[\\s\\S]*\\S)[\\s\\S]*(?![\\s\\S])" },
            { "type": "null" }
          ],
          "default": "code",
          "description": "Executable name or path, resolved through PATH when applicable. Never parsed as a shell command. Null leaves the viewer unconfigured. Windows .cmd launchers require an actual executable instead."
        },
        "args": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[^\\u0000]*(?![\\s\\S])" },
          "default": [],
          "description": "Literal arguments. The absolute command-file path is appended as the last argument. No shell expansion."
        },
        "mode": {
          "type": "string",
          "enum": ["detach", "wait"],
          "default": "detach",
          "description": "Detach starts without terminal access or waiting. Wait suspends the Pi TUI and main event loop until the editor exits."
        },
        "filePath": {
          "anyOf": [
            {
              "type": "string",
              "pattern": "^(?![\\s\\S]*\\u0000)(?:/|[A-Za-z]:[\\\\/]|\\\\\\\\[^\\\\/]+[\\\\/][^\\\\/]+(?:[\\\\/]|$))[\\s\\S]*(?![\\s\\S])"
            },
            { "type": "null" }
          ],
          "default": null,
          "description": "Directory for session command files. Accepts POSIX or Windows absolute-path syntax; it must also be absolute on the running system when opened. Null uses pi-permission-bash-cmd-checker under the system temporary directory. Files use command-<sessionId>.sh, are overwritten per session, and are not automatically deleted."
        }
      }
    },
    "autoBlockUnsafe": {
      "type": "boolean",
      "default": false,
      "description": "Deny only validated unsafe classifications. All other outcomes defer; never automatically allow."
    }
  },
  "$defs": {
    "model": {
      "type": "object",
      "additionalProperties": false,
      "required": ["provider", "id"],
      "properties": {
        "provider": { "type": "string", "pattern": "\\S" },
        "id": { "type": "string", "pattern": "\\S" }
      }
    },
    "timeout": { "type": "integer", "minimum": 1, "maximum": 2147483647 },
    "probability": { "type": "number", "minimum": 0, "maximum": 1 }
  }
}
