{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tiinyapp.farm/docs/manifest.schema.json",
  "title": "tiinyapp.farm app manifest",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "name",
    "pitch",
    "description",
    "version",
    "author",
    "license",
    "screenshots",
    "entry",
    "requires",
    "permissions",
    "tags",
    "verified",
    "addedAt",
    "updatedAt"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?![\\s\\S])",
      "not": {
        "const": "tiiny"
      }
    },
    "name": {
      "type": "string",
      "minLength": 1
    },
    "pitch": {
      "type": "string",
      "minLength": 1,
      "pattern": "^[^\\r\\n]+(?![\\s\\S])"
    },
    "description": {
      "type": "string",
      "minLength": 1
    },
    "version": {
      "type": "string",
      "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?![\\s\\S])"
    },
    "author": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "url"
      ],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "url": {
          "$ref": "#/$defs/webUrl"
        },
        "tiinyverse": {
          "type": "string",
          "pattern": "^https://www\\.tiinyverse\\.com/users/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(?![\\s\\S])",
          "description": "Verified owner profile. Required for every new or changed submission by the CI owner gate; legacy catalog drafts remain readable."
        }
      }
    },
    "license": {
      "type": "string",
      "minLength": 1
    },
    "homepage": {
      "$ref": "#/$defs/webUrl"
    },
    "repo": {
      "$ref": "#/$defs/webUrl",
      "description": "Optional source repository URL; an uploaded source archive is sufficient."
    },
    "screenshots": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/webUrl"
      },
      "uniqueItems": true
    },
    "release": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "url",
        "sha256",
        "size"
      ],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "pattern": "^(https?://|file://)"
        },
        "sha256": {
          "type": "string",
          "pattern": "^([a-f0-9]{64}|pending)(?![\\s\\S])"
        },
        "size": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "entry": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "python",
            "args"
          ],
          "properties": {
            "python": {
              "type": "string",
              "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*(?![\\s\\S])"
            },
            "args": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "command"
          ],
          "properties": {
            "command": {
              "type": "string",
              "minLength": 1
            }
          }
        }
      ]
    },
    "requires": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "ports",
        "device"
      ],
      "properties": {
        "python": {
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+(?![\\s\\S])"
        },
        "ports": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535
          }
        },
        "device": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "models",
            "npuUnits"
          ],
          "properties": {
            "models": {
              "type": "array",
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "npuUnits": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    },
    "permissions": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "enum": [
          "microphone",
          "files",
          "network",
          "device"
        ]
      }
    },
    "tags": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "verified": {
      "type": "boolean",
      "description": "Only a human maintainer sets true in a follow-up commit after CI and manual review."
    },
    "featured": {
      "type": "boolean",
      "description": "Optional maintainer-curated placement in the home page Featured section."
    },
    "addedAt": {
      "type": "string",
      "format": "date"
    },
    "updatedAt": {
      "type": "string",
      "format": "date"
    },
    "health": {
      "type": "string",
      "pattern": "^/(?!/)[A-Za-z0-9_./-]+(?![\\s\\S])",
      "description": "Optional HTTP health path on the first declared port; GET returns a JSON object with version and optional ok."
    },
    "selfcheck": {
      "type": "boolean",
      "description": "If true, CI runs the entry with --selfcheck appended, offline, with a 120 second limit. Requires a runnable entry."
    },
    "media": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "icon": {
          "$ref": "#/$defs/imageUrl"
        },
        "header": {
          "$ref": "#/$defs/imageUrl"
        },
        "gallery": {
          "type": "array",
          "maxItems": 8,
          "items": {
            "$ref": "#/$defs/imageUrl"
          },
          "uniqueItems": true
        }
      }
    },
    "links": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "repo": {
          "$ref": "#/$defs/httpsUrl"
        },
        "homepage": {
          "$ref": "#/$defs/httpsUrl"
        },
        "video": {
          "type": "string",
          "format": "uri",
          "pattern": "^https://(?:(?:www\\.)?youtube\\.com/watch\\?(?:[^#\\s&]+&)*v=[A-Za-z0-9_-]{11}(?:&[^#\\s]*)?|youtu\\.be/[A-Za-z0-9_-]{11}(?:\\?[^#\\s]*)?)(?:#[^\\s]*)?(?![\\s\\S])",
          "maxLength": 2048
        }
      }
    }
  },
  "$defs": {
    "webUrl": {
      "type": "string",
      "format": "uri",
      "pattern": "^https?://"
    },
    "httpsUrl": {
      "type": "string",
      "format": "uri",
      "maxLength": 2048,
      "pattern": "^https://[^\\s/@]+(?:/[^\\s]*)?(?![\\s\\S])"
    },
    "imageUrl": {
      "oneOf": [
        {
          "$ref": "#/$defs/httpsUrl"
        },
        {
          "type": "string",
          "maxLength": 2048,
          "pattern": "^/(?:assets|media)/[A-Za-z0-9_./-]+(?![\\s\\S])"
        }
      ]
    }
  }
}
