{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://app.clawnify.com/schema/v1/clawnify.json",
  "title": "Clawnify App Manifest",
  "description": "Manifest file for repos deployable via the Clawnify deploy button. Place as clawnify.json in your repo root.",
  "type": "object",
  "required": ["name", "description", "app"],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "JSON Schema reference for editor autocomplete."
    },
    "version": {
      "type": "integer",
      "const": 1,
      "description": "Schema version. Currently must be 1."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 60,
      "description": "Human-readable app name shown on the deploy page."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500,
      "description": "One-paragraph description of what the app does. Shown on the deploy page and used for template matching."
    },
    "icon": {
      "type": "string",
      "description": "Path to an SVG icon file relative to the repo root.",
      "default": "icon.svg"
    },
    "screenshot": {
      "type": "string",
      "description": "URL or relative path to a screenshot/preview image."
    },
    "tags": {
      "type": "array",
      "items": { "type": "string", "maxLength": 30 },
      "maxItems": 10,
      "description": "Tags for discovery and categorization."
    },
    "app": {
      "type": "object",
      "required": ["framework"],
      "additionalProperties": false,
      "description": "Build configuration for the app.",
      "properties": {
        "framework": {
          "type": "string",
          "enum": ["preact+hono", "react+hono", "vite-preact", "vite-react", "static"],
          "description": "Framework used by the app. Determines the build pipeline."
        },
        "database": {
          "type": "boolean",
          "default": true,
          "description": "Whether the app needs a database. Schema is read from src/server/schema.sql."
        },
        "storage": {
          "type": "boolean",
          "default": false,
          "description": "Whether the app needs file storage for uploads."
        },
        "files": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Explicit list of source files. If omitted, all files in src/ are included."
        },
        "credentials": {
          "type": "array",
          "items": { "type": "string" },
          "description": "OAuth integrations to wire up via the Clawnify credential service binding (e.g. ['twitter', 'github']). The build injects a CREDENTIALS service binding and CLAWNIFY_ORG_ID env var, and surfaces api_key/basic_auth credentials as Worker secrets."
        },
        "r2": {
          "type": "boolean",
          "default": false,
          "description": "Alias of `storage`. Whether the app needs an R2 bucket for uploads."
        }
      }
    },
    "env": {
      "type": "object",
      "additionalProperties": false,
      "description": "Environment variables the app needs at runtime. Names are matched against the org's API Keys and Environment Variables; matches are injected as Worker secrets at deploy time.",
      "properties": {
        "required": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Env vars that MUST all be present in the org's keys/env. Deploy is reported with a warning if any are missing."
        },
        "oneOf": {
          "type": "array",
          "items": {
            "type": "array",
            "items": { "type": "string" },
            "minItems": 1
          },
          "description": "Groups where at least one var per group must be present (e.g. [['OPENROUTER_API_KEY', 'OPENAI_API_KEY']] means either OpenRouter or OpenAI works)."
        },
        "optional": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Env vars to inject if available; no warning if missing."
        }
      }
    },
    "deploy": {
      "type": "object",
      "additionalProperties": false,
      "description": "Configuration for the deploy page UI and customization flow.",
      "properties": {
        "title": {
          "type": "string",
          "maxLength": 80,
          "description": "Heading shown on the deploy page. Defaults to 'Deploy {name}'."
        },
        "subtitle": {
          "type": "string",
          "maxLength": 200,
          "description": "Subheading shown below the title on the deploy page."
        },
        "prompts": {
          "type": "array",
          "maxItems": 6,
          "description": "Questions shown to the user before deploy. Answers are passed to the AI agent for customization.",
          "items": {
            "type": "object",
            "required": ["key", "label"],
            "additionalProperties": false,
            "properties": {
              "key": {
                "type": "string",
                "pattern": "^[a-z_][a-z0-9_]*$",
                "description": "Machine-readable key for this prompt. Used in the customization payload."
              },
              "label": {
                "type": "string",
                "maxLength": 100,
                "description": "Human-readable label shown above the input."
              },
              "placeholder": {
                "type": "string",
                "maxLength": 200,
                "description": "Placeholder text inside the input."
              },
              "type": {
                "type": "string",
                "enum": ["text", "textarea", "select"],
                "default": "text",
                "description": "Input type. 'text' for short answers, 'textarea' for long-form, 'select' for dropdown."
              },
              "options": {
                "type": "array",
                "items": { "type": "string" },
                "description": "Options for 'select' type prompts."
              },
              "required": {
                "type": "boolean",
                "default": false,
                "description": "Whether the user must fill this in before deploying."
              }
            }
          }
        }
      }
    },
    "agent": {
      "type": "string",
      "default": "agent.md",
      "description": "Path to the agent instructions file (relative to repo root). The AI agent reads this to understand how to customize the app."
    },
    "api": {
      "type": "object",
      "additionalProperties": false,
      "description": "Platform-level HTTP surface declarations. Default is fully gated by the Clawnify perimeter; declare routes here to opt specific paths into being publicly reachable. See docs/app-public-routes.md.",
      "properties": {
        "public_routes": {
          "type": "array",
          "maxItems": 100,
          "description": "Paths reachable without Clawnify perimeter auth. Each entry is either a glob string (any method) or an object {path, methods?[]} for method-specific exposure. OPTIONS is auto-allowed alongside any listed method (CORS preflight). Default empty = fully gated.",
          "items": {
            "oneOf": [
              {
                "type": "string",
                "description": "Glob path matched for any HTTP method. Patterns: '/path' exact, '/path/*' one segment, '/path/**' zero-or-more, '/og/*.png' filename glob.",
                "pattern": "^/[^?#\\s]*$",
                "not": { "enum": ["/", "/*", "/**"] }
              },
              {
                "type": "object",
                "required": ["path"],
                "additionalProperties": false,
                "properties": {
                  "path": {
                    "type": "string",
                    "pattern": "^/[^?#\\s]*$",
                    "not": { "enum": ["/", "/*", "/**"] }
                  },
                  "methods": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
                    }
                  }
                }
              }
            ]
          }
        },
        "automation_bypass": {
          "type": "boolean",
          "default": false,
          "description": "When true, mint a per-app secret at deploy time. Requests carrying `X-Clawnify-Bypass-Secret: <secret>` short-circuit perimeter auth on any path. For non-listable webhook URLs. Surfaced once in the dashboard at creation; regenerate after."
        }
      }
    }
  }
}
