{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://sparcle.app/bolt-app.schema.json",
  "title": "Bolt App (User App YAML file)",
  "description": "This schema is documentation, not an enforcement boundary: it describes the shape of a Bolt App so a person or an agent can get it right. Bolt itself is what refuses a file that breaks a rule. Structure of one Bolt App: a YAML file in the User Apps folder of the Bolt desktop app, or inside a .boltpack. Unknown fields are refused. This schema checks structure; Bolt also checks rules a schema cannot say (a command is split into arguments and run without a shell, a {{variable}} is never inside quotes, a recognize regex must be a valid pattern). Pane kinds and transform steps that load but do nothing today are left out on purpose. Guide: https://sparcle.app/docs/extend/agent-guide/",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema_version", "runtime", "id", "chip", "title", "emits", "presentation"],
  "properties": {
    "schema_version": { "const": 1 },
    "runtime": { "enum": ["transform", "http_local", "bridge"], "description": "transform: reshape text, no command and no network. http_local: one GET or POST to a service on this computer. bridge: run one program and turn its output into rows." },
    "id": { "$ref": "#/$defs/key", "description": "Also the file name: <id>.yaml." },
    "chip": { "$ref": "#/$defs/key", "description": "The key typed after =." },
    "title": { "type": "string", "pattern": "\\S" },
    "icon": { "type": "string" },
    "description": { "type": "string" },
    "placeholder_examples": { "type": "array", "items": { "type": "string" } },
    "emits": { "enum": ["Person", "Issue", "Document", "Event", "File", "Incident", "Account", "Place", "Generic"], "description": "File makes a row with an absolute path act as a file. Use Generic when unsure." },
    "args": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "from"],
        "properties": {
          "name": { "type": "string", "pattern": "\\S" },
          "from": { "type": "string", "pattern": "^\\s*query\\.word\\(\\s*\\d+\\s*\\)\\s*$", "description": "Only query.word(N) exists: the Nth word typed after the key, counting from 0." },
          "default": {}
        }
      }
    },
    "presentation": { "$ref": "#/$defs/presentation" },
    "actions": { "type": "array", "items": { "$ref": "#/$defs/action" } },
    "dispatch": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "mode": { "const": "confirm" },
        "confirm_label": { "type": "string" },
        "confirm_hint": { "type": "string" },
        "choices": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "label"],
            "properties": {
              "id": { "type": "string", "pattern": "\\S" },
              "label": { "type": "string", "pattern": "\\S" },
              "hint": { "type": "string" },
              "args": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Each key must be the name of an entry in the top-level args." }
            }
          }
        }
      },
      "if": { "required": ["choices"], "properties": { "choices": { "minItems": 1 } } },
      "then": { "not": { "anyOf": [{ "required": ["confirm_label"] }, { "required": ["confirm_hint"] }] } }
    },
    "detect": {
      "type": "object",
      "additionalProperties": false,
      "required": ["binary"],
      "properties": {
        "binary": { "type": "string", "pattern": "^[^/\\\\\\u0000\\s][^/\\\\\\u0000]*$", "description": "A program name looked up on PATH, never a path." },
        "version_min": { "type": "string" },
        "auth_check": { "$ref": "#/$defs/osCommand" },
        "config_hint": { "type": "array", "items": { "type": "string" } },
        "refresh": { "enum": ["on_boot", "on_focus", "manual"] },
        "setup": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "unlock": { "type": "string", "pattern": "\\S" },
            "signin": { "$ref": "#/$defs/osText" },
            "install": { "$ref": "#/$defs/osText" }
          }
        }
      }
    },
    "inputs": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "type", "label"],
        "properties": {
          "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
          "type": { "enum": ["text", "number", "date", "select", "toggle", "secret"] },
          "label": { "type": "string", "pattern": "\\S" },
          "required": { "type": "boolean" },
          "default": {},
          "placeholder": { "type": "string" },
          "options": { "type": "array", "items": { "type": "string" } },
          "min": { "type": "number" },
          "max": { "type": "number" },
          "step": { "type": "number" },
          "max_length": { "type": "integer", "minimum": 0 },
          "pattern": { "type": "string", "pattern": "\\S", "maxLength": 512 }
        },
        "if": { "properties": { "type": { "const": "select" } }, "required": ["type"] },
        "then": { "required": ["options"], "properties": { "options": { "minItems": 1 } } }
      }
    },
    "submit": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "label": { "type": "string" }, "confirm": { "type": "boolean" } }
    },
    "recognize": {
      "type": "object",
      "additionalProperties": false,
      "required": ["patterns"],
      "properties": {
        "patterns": {
          "type": "array",
          "minItems": 1,
          "maxItems": 16,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["regex"],
            "properties": {
              "regex": { "type": "string", "pattern": "\\S", "maxLength": 512 },
              "flags": { "type": "string", "pattern": "^[imsu]*$", "description": "Any of i, m, s, u. g and y are refused." },
              "priority": { "type": "integer" }
            }
          }
        },
        "entity": { "$ref": "#/$defs/emits" },
        "confidence": { "enum": ["ambient", "high"] },
        "anchors": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "min_len": { "type": "integer", "minimum": 0 },
            "require": { "type": "array", "items": { "type": "string" } }
          }
        },
        "reps": {
          "type": "array",
          "maxItems": 12,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["label", "template"],
            "properties": { "label": { "type": "string", "pattern": "\\S" }, "template": { "type": "string", "pattern": "\\S" } }
          }
        }
      }
    },
    "transform": {
      "type": "object",
      "additionalProperties": false,
      "required": ["pipe"],
      "properties": {
        "pipe": {
          "type": "array",
          "minItems": 1,
          "maxItems": 32,
          "items": {
            "oneOf": [
              { "enum": ["lines", "sort", "dedupe", "reverse", "trim", "upper", "lower", "title", "table", "markdown", "csv", "aligned", "sql", "json", "transpose", "sanitize"] },
              { "type": "object", "additionalProperties": false, "required": ["split"], "properties": { "split": { "type": "string" } } },
              { "type": "object", "additionalProperties": false, "required": ["join"], "properties": { "join": { "type": "string" } } },
              { "type": "object", "additionalProperties": false, "required": ["prefix"], "properties": { "prefix": { "type": "string" } } },
              { "type": "object", "additionalProperties": false, "required": ["suffix"], "properties": { "suffix": { "type": "string" } } },
              { "type": "object", "additionalProperties": false, "required": ["template"], "properties": { "template": { "type": "string", "description": "{value} is replaced with the current text." } } },
              { "type": "object", "additionalProperties": false, "required": ["head"], "properties": { "head": { "type": "integer", "minimum": 0 } } },
              { "type": "object", "additionalProperties": false, "required": ["tail"], "properties": { "tail": { "type": "integer", "minimum": 0 } } },
              { "type": "object", "additionalProperties": false, "required": ["slice"], "properties": { "slice": { "type": "object", "additionalProperties": false, "properties": { "from": { "type": "integer", "minimum": 0 }, "to": { "type": "integer", "minimum": 0 } } } } },
              { "type": "object", "additionalProperties": false, "required": ["replace"], "properties": { "replace": { "type": "object", "additionalProperties": false, "required": ["from", "to"], "properties": { "from": { "type": "string", "maxLength": 512, "description": "A regular expression. Nested unbounded repeats such as (a+)+ are refused." }, "to": { "type": "string" }, "flags": { "type": "string", "pattern": "^[gimsu]*$", "description": "Default g." } } } } },
              { "type": "object", "additionalProperties": false, "required": ["filter"], "properties": { "filter": { "type": "object", "minProperties": 1, "maxProperties": 1, "additionalProperties": false, "properties": { "contains": { "type": "string" }, "not_contains": { "type": "string" }, "matches": { "type": "string", "maxLength": 512 }, "not_matches": { "type": "string", "maxLength": 512 } } } } },
              { "type": "object", "additionalProperties": false, "required": ["sortcol"], "properties": { "sortcol": { "type": ["string", "integer"], "minimum": 0 } } },
              { "type": "object", "additionalProperties": false, "required": ["dedupecol"], "properties": { "dedupecol": { "type": ["string", "integer"], "minimum": 0 } } },
              { "type": "object", "additionalProperties": false, "required": ["pick"], "properties": { "pick": { "type": "array", "items": { "type": "string" } } } },
              { "type": "object", "additionalProperties": false, "required": ["omit"], "properties": { "omit": { "type": "array", "items": { "type": "string" } } } }
            ]
          }
        }
      }
    },
    "http": {
      "type": "object",
      "additionalProperties": false,
      "required": ["port", "request", "parse"],
      "properties": {
        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
        "host": { "enum": ["127.0.0.1", "::1", "localhost"], "description": "Loopback only. Defaults to 127.0.0.1." },
        "request": {
          "type": "object",
          "additionalProperties": false,
          "required": ["path"],
          "properties": {
            "method": { "enum": ["GET", "POST", "get", "post"] },
            "path": { "type": "string", "pattern": "^/(?!/)[^\\u0000-\\u001F\\u007F\\\\]*$", "description": "Starts with / and must not start with // (a protocol-relative address), must not contain a backslash or a control character. {{var}} values are percent-encoded. Bolt must parse the final URL and require the hostname and the port to equal the configured loopback host and port, and must check every redirect target against the same loopback origin." }
          }
        },
        "parse": { "enum": ["json", "lines", "raw"] },
        "rows_path": { "type": "string", "description": "Dotted path to the array of rows inside a JSON reply, such as models or result.items." },
        "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 60 },
        "headers": {
          "type": "object",
          "propertyNames": { "pattern": "^[A-Za-z0-9-]+$" },
          "additionalProperties": { "type": "string", "description": "A literal, or exactly one ${env:NAME} where NAME is on the allowed list in the guide." }
        }
      }
    },
    "bridge": {
      "type": "object",
      "additionalProperties": false,
      "required": ["command", "parse"],
      "properties": {
        "command": { "$ref": "#/$defs/bridgeCommand" },
        "parse": { "enum": ["json", "lines", "raw"] },
        "timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 60, "description": "Default 10." },
        "env": {
          "type": "object",
          "propertyNames": { "pattern": "^[A-Z_][A-Z0-9_]*$" },
          "additionalProperties": { "type": "string", "pattern": "^\\$\\{env:[A-Z][A-Z0-9_]{0,63}\\}$", "description": "Exactly one ${env:NAME}, with NAME on the allowed list, under its own name." }
        },
        "env_passthrough": {
          "type": "array",
          "maxItems": 16,
          "uniqueItems": true,
          "items": { "enum": ["GH_HOST", "GH_REPO", "GITLAB_HOST", "DOCKER_CONTEXT", "DOCKER_TLS_VERIFY", "AWS_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_CA_BUNDLE", "AWS_ENDPOINT_URL", "CLOUDSDK_CORE_PROJECT", "OKTA_CLIENT_ORGURL", "SF_TARGET_ORG", "SF_ORG_INSTANCE_URL", "TWILIO_ACCOUNT_SID", "TWILIO_REGION", "TWILIO_EDGE", "VERCEL_ORG_ID", "VERCEL_PROJECT_ID", "PGHOST", "PGPORT", "PGUSER", "PGDATABASE", "PGSERVICE", "PGSSLMODE"] },
          "description": "Environment variables the program may read. Credentials, tokens and proxy settings are never forwarded to your own Apps."
        },
        "cwd": { "enum": ["home"], "description": "Absent: a fresh private folder for each run." }
      }
    }
  },
  "allOf": [
    { "if": { "properties": { "runtime": { "const": "transform" } }, "required": ["runtime"] }, "then": { "required": ["transform"], "not": { "anyOf": [{ "required": ["bridge"] }, { "required": ["http"] }] } } },
    { "if": { "properties": { "runtime": { "const": "http_local" } }, "required": ["runtime"] }, "then": { "required": ["http"], "not": { "required": ["bridge"] } } },
    { "if": { "properties": { "runtime": { "const": "bridge" } }, "required": ["runtime"] }, "then": { "required": ["bridge"], "not": { "required": ["http"] } } }
  ],
  "$defs": {
    "key": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
    "emits": { "enum": ["Person", "Issue", "Document", "Event", "File", "Incident", "Account", "Place", "Generic"] },
    "osText": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": { "macos": { "type": "string", "pattern": "\\S" }, "linux": { "type": "string", "pattern": "\\S" }, "windows": { "type": "string", "pattern": "\\S" } }
    },
    "argvString": {
      "type": "string",
      "pattern": "^(?:[^|{;&`$\\s]|\\s|&(?!&)|\\$(?!\\()|\\{(?!\\{)|\\{\\{.*?\\}\\})*$",
      "description": "One program and its arguments. No ; && || | backtick or $( outside a {{variable}}. A {{variable}} is written bare, never inside quotes. No ~ expansion: use {{home}}."
    },
    "osCommand": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": { "macos": { "$ref": "#/$defs/argvString" }, "linux": { "$ref": "#/$defs/argvString" }, "windows": { "$ref": "#/$defs/argvString" } }
    },
    "bridgeCommand": { "$ref": "#/$defs/osCommand" },
    "action": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "kind", "label"],
      "properties": {
        "id": { "type": "string", "pattern": "\\S" },
        "kind": { "enum": ["url", "composer", "utility"] },
        "label": { "type": "string", "pattern": "\\S", "description": "The footer button shows at most 14 characters." },
        "target": { "type": "string", "description": "row, or list (a utility action with target: list routes every row)." },
        "url_template": { "type": "string", "pattern": "^(?:https?://[^\\u0000-\\u001F\\u007F]*|\\{\\{[^{}\\u0000-\\u001F\\u007F]*\\}\\})$", "description": "A web address template. It must be either a literal http:// or https:// address, or a single {{row.field}} placeholder. No other scheme can be written here: javascript:, data:, file: and vbscript: are refused, and control characters are refused. Bolt must still parse the rendered URL and refuse any scheme other than http or https." },
        "insert": { "type": "string" },
        "target_chip": { "type": "string" },
        "query_template": { "type": "string" },
        "primary": { "type": "boolean", "description": "At most one action may be primary." }
      },
      "allOf": [
        { "if": { "properties": { "kind": { "const": "url" } } }, "then": { "required": ["url_template"], "not": { "anyOf": [{ "required": ["target_chip"] }, { "required": ["query_template"] }] } } },
        { "if": { "properties": { "kind": { "const": "composer" } } }, "then": { "required": ["insert"], "not": { "anyOf": [{ "required": ["target_chip"] }, { "required": ["query_template"] }] } } },
        { "if": { "properties": { "kind": { "const": "utility" } } }, "then": { "required": ["target_chip"], "not": { "anyOf": [{ "required": ["url_template"] }, { "required": ["insert"] }] } } }
      ]
    },
    "text": { "type": "string", "maxLength": 4096 },
    "name": { "type": "string", "maxLength": 200 },
    "body": { "type": "string", "maxLength": 16384 },
    "actionIds": { "type": "array", "maxItems": 16, "items": { "type": "string", "maxLength": 200 }, "description": "Ids of entries in the top-level actions." },
    "presentation": {
      "type": "object",
      "additionalProperties": false,
      "required": ["widget"],
      "properties": {
        "widget": { "enum": ["list", "table", "cards", "detail"], "description": "How the result list on the left looks." },
        "title_field": { "type": "string", "description": "Row field shown as the title. parse: lines rows have line and idx." },
        "subtitle_field": { "type": "string" },
        "searchable": { "type": "boolean" },
        "right_widget": {
          "type": "object",
          "additionalProperties": false,
          "required": ["kind"],
          "properties": {
            "kind": { "enum": ["card", "markdown", "entity_preview", "chart", "table", "cards", "detail", "json_tree", "diff", "image_grid"], "description": "map, image, calc, color, weather and date pass validation but show no app data, so they are left out." },
            "mode": { "enum": ["selected_row", "list_data"], "description": "selected_row: the highlighted row. list_data: all rows." },
            "bind": { "type": "object" }
          },
          "allOf": [
            { "if": { "properties": { "kind": { "const": "card" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindCard" } } } },
            { "if": { "properties": { "kind": { "const": "markdown" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindMarkdown" } } } },
            { "if": { "properties": { "kind": { "const": "entity_preview" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindEntityPreview" } } } },
            { "if": { "properties": { "kind": { "const": "chart" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindChart" } } } },
            { "if": { "properties": { "kind": { "const": "table" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindTable" } } } },
            { "if": { "properties": { "kind": { "const": "cards" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindCards" } } } },
            { "if": { "properties": { "kind": { "const": "detail" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindDetail" } } } },
            { "if": { "properties": { "kind": { "const": "json_tree" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindJsonTree" } } } },
            { "if": { "properties": { "kind": { "const": "diff" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindDiff" } } } },
            { "if": { "properties": { "kind": { "const": "image_grid" } } }, "then": { "properties": { "bind": { "$ref": "#/$defs/bindImageGrid" } } } }
          ]
        }
      }
    },
    "bindCard": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "title": { "$ref": "#/$defs/text" }, "subtitle": { "$ref": "#/$defs/text" }, "actions": { "$ref": "#/$defs/actionIds" } }
    },
    "bindMarkdown": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "title": { "$ref": "#/$defs/text" }, "subtitle": { "$ref": "#/$defs/text" }, "body": { "$ref": "#/$defs/body" }, "actions": { "$ref": "#/$defs/actionIds" } }
    },
    "bindEntityPreview": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "title": { "$ref": "#/$defs/text" },
        "subtitle": { "$ref": "#/$defs/text" },
        "image_url": { "$ref": "#/$defs/text" },
        "fields": {
          "type": "array",
          "maxItems": 40,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "label": { "$ref": "#/$defs/name" },
              "value": { "$ref": "#/$defs/text" },
              "kind": { "enum": ["text", "badge", "chips", "branch", "diff"] },
              "tone": { "enum": ["merged", "open", "closed", "draft", "success", "warn", "danger", "neutral"] }
            }
          }
        },
        "body": { "$ref": "#/$defs/body" },
        "actions": { "$ref": "#/$defs/actionIds" }
      }
    },
    "bindChart": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "type": { "enum": ["line", "bar", "sparkline"] },
        "x": { "$ref": "#/$defs/name" },
        "y": { "$ref": "#/$defs/name" },
        "values": { "$ref": "#/$defs/name" },
        "label": { "$ref": "#/$defs/name" },
        "color": { "enum": ["accent", "muted"] },
        "unit": { "$ref": "#/$defs/name" }
      }
    },
    "bindTable": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "columns": {
          "type": "array",
          "maxItems": 40,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["field"],
            "properties": {
              "field": { "$ref": "#/$defs/name" },
              "label": { "$ref": "#/$defs/name" },
              "align": { "enum": ["left", "right", "center"] },
              "total": { "enum": ["sum", "avg", "min", "max", "count", "none"], "description": "Rich widgets." },
              "format": { "enum": ["text", "number", "percent", "currency", "date"], "description": "Rich widgets." },
              "unit": { "$ref": "#/$defs/name" }
            }
          }
        },
        "sort": {
          "type": "object",
          "additionalProperties": false,
          "required": ["field"],
          "properties": { "field": { "$ref": "#/$defs/name" }, "dir": { "enum": ["asc", "desc"] } },
          "description": "Rich widgets."
        },
        "totals_label": { "$ref": "#/$defs/name", "description": "Rich widgets." }
      }
    },
    "bindCards": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "title": { "$ref": "#/$defs/text" },
        "subtitle": { "$ref": "#/$defs/text" },
        "badge": { "$ref": "#/$defs/text" },
        "image": { "$ref": "#/$defs/text" },
        "footer": { "$ref": "#/$defs/text" },
        "layout": { "enum": ["grid", "tiles"], "description": "tiles needs Rich widgets, and needs value. With the switch off the same bind draws the grid." },
        "value": { "$ref": "#/$defs/text" },
        "unit": { "$ref": "#/$defs/text" },
        "delta": { "$ref": "#/$defs/text" },
        "delta_label": { "$ref": "#/$defs/text" },
        "max": { "type": ["string", "number"], "maxLength": 4096, "description": "Draws a meter (gauge) from value to max." },
        "min": { "type": ["string", "number"], "maxLength": 4096 },
        "trend": { "$ref": "#/$defs/text" }
      },
      "if": { "required": ["layout"], "properties": { "layout": { "const": "tiles" } } },
      "then": { "required": ["value"] }
    },
    "bindDetail": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "title": { "$ref": "#/$defs/text" },
        "subtitle": { "$ref": "#/$defs/text" },
        "fields": {
          "type": "array",
          "maxItems": 40,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": { "label": { "$ref": "#/$defs/name" }, "value": { "$ref": "#/$defs/text" }, "copy": { "type": "boolean" } }
          }
        }
      }
    },
    "bindJsonTree": { "type": "object", "additionalProperties": false, "properties": { "source": { "$ref": "#/$defs/name" } } },
    "bindDiff": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "left": { "$ref": "#/$defs/text" }, "right": { "$ref": "#/$defs/text" }, "leftLabel": { "$ref": "#/$defs/name" }, "rightLabel": { "$ref": "#/$defs/name" } }
    },
    "bindImageGrid": {
      "type": "object",
      "additionalProperties": false,
      "properties": { "src": { "$ref": "#/$defs/text" }, "caption": { "$ref": "#/$defs/text" }, "alt": { "$ref": "#/$defs/text" }, "columns": { "type": "integer", "minimum": 1, "maximum": 8 } }
    }
  }
}
