{
  "name": "pricklypear",
  "version": 3,
  "philosophy": "The image is the API. Eval Lisp on the running habitat; do not invent REST endpoints for app features (todos, notes, bible, …).",
  "live_docs": {
    "agents_note": {
      "slug": "agents",
      "how": "(load-library \"notes\") then (notes/doc/get \"agents\"). Living AGENTS.md for this habitat; agents may notes/doc/upsert it.",
      "browser_path": "/notes?slug=agents"
    },
    "wiki_schema": {
      "slug": "wiki-schema",
      "how": "(wiki/schema) or (notes/doc/get \"wiki-schema\"). LLM preamble for wiki ingest/query/lint."
    }
  },
  "base_url": "https://pricklypear.rocks",
  "auth": {
    "scheme": "HTTP Basic",
    "realm": "image credentials table (same username/password as /login)",
    "how_to_get_credentials": "Human supplies username + password once. Create users at /admin/users after bootstrap login. Do not scrape passwords from this document — there are none.",
    "login_url": "/login",
    "session_cookie": "Browser clients may use cookie pp_session (HttpOnly) after POST /login; agents usually use Basic auth.",
    "notes": [
      "Use the image password, not nginx/edge htpasswd.",
      "No OAuth/JWT in v1.", "Never log or commit the password."
    ]
  },
  "endpoints": {
    "eval": {
      "method": "POST",
      "path": "/api/eval",
      "auth": "required",
      "content_type": "application/json",
      "request": { "expr": "(+ 1 2)" },
      "response_ok": { "ok": true, "value": "3" },
      "response_err": { "ok": false, "error": "unbound: foo" }
    },
    "health": { "method": "GET", "path": "/health", "auth": "none" },
    "media_upload": {
      "method": "POST",
      "path": "/api/media",
      "auth": "required",
      "content_type": "image/*|application/pdf (raw body); application/json with data_b64; or multipart/form-data field name=file",
      "max_bytes": 5242880,
      "headers": {
        "X-Filename": "optional original filename (raw body mode)"
      },
      "response_ok": {
        "ok": true,
        "id": "uuid",
        "url": "/media/<uuid>",
        "content_type": "image/png",
        "byte_size": 1234,
        "sha256": "hex"
      },
      "client": "scripts/pp_media.py put ./shot.png"
    },
    "media_get": {
      "method": "GET",
      "path": "/media/<uuid>",
      "auth": "required",
      "returns": "raw bytes with stored Content-Type"
    },
    "media_list": {
      "method": "GET",
      "path": "/api/media",
      "auth": "required",
      "returns": "JSON meta only (no bytes)"
    },
    "media_browser": {
      "method": "GET",
      "path": "/media",
      "auth": "session or basic",
      "upload": "POST /media/upload multipart field name=file"
    },
    "repl_ui": {
      "method": "GET",
      "path": "/repl",
      "auth": "session or basic"
    },
    "discovery_json": {
      "method": "GET",
      "path": "/.well-known/agent.json",
      "auth": "none"
    },
    "discovery_text": {
      "method": "GET",
      "path": "/agent.txt",
      "auth": "none"
    }
  },
  "bootstrap": {
    "first_calls": [
      "(whoami)", "(current-branch)", "(list-packages)",
      "(load-library \"notes\")", "(notes/doc/get \"agents\")",
      "(load-library \"todo\")", "(describe \"todo\")",
      "(apropos-docs \"todo/item\")", "(function-doc \"todo/item/add\")",
      "(load-library \"bible\")", "(bible/chapter \"John\" 3)"
    ],
    "rules": [
      "One Lisp expression per POST /api/eval unless you own multi-step state yourself.",
      "Load libraries before calling package functions: (load-library \"todo\").",
      "Prefer a personal branch before defining things: (checkout \"USER/personal\").",
      "Two function kinds: DATA builders take explicit args and work from /api/eval (e.g. bible/chapter, bible/chapter-html with book/chapter); HTTP HANDLERS (*/page, */reader) use request-method/request-param and return \"no active request\" outside a browser hit — that is expected, not a crash.",
      "Do not invent app-specific REST APIs — call Lisp instead.",
      "Never put passwords in code you write back to the image.",
      "Unauthenticated /api/eval is rejected; /agent (HTML portal) is private.",
      "Dotted rest params (a . b) are NOT supported; use fixed-arity params.",
      "File product issues as notes in the inbox when you cannot fix OCaml; do not invent REST for filing.",
      "After (load-library \"notes\"), read the live agents note: (notes/doc/get \"agents\"). It is the habitat AGENTS.md — edit it with notes/doc/upsert when agent guidance should change.",
      "Discover selectively — do not dump all bindings into context: (list-packages), (describe \"pkg\"), (package-bindings \"pkg\"), (package-exports \"pkg\"), (apropos-docs \"pat\"), (function-doc name).",
      "Build a REPL client from this document: HTTP Basic + POST /api/eval JSON {\"expr\":\"…\"}. Prefer that over inventing REST. curl and Python examples are below; local hosts may use the pp-eval CLI.",
      "Knowledge wiki (Karpathy loop): (wiki/ingest-from-source …), (wiki/query …), (wiki/lint); wiki links are [[slug]] on outline bodies.",
      "Binary media (images/PDF): do NOT push multi-KiB bytes through /api/eval (64 KiB cap). Use POST /api/media (Basic auth; raw body, JSON data_b64, or multipart field name=file; up to 5 MiB) or scripts/pp_media.py. Browser UI: GET /media + POST /media/upload. Serve via GET /media/<id>. Meta via (media-meta id) / (media-list); tiny fixtures only via (media-put …)."
    ],
    "discovery": {
      "list_packages": "(list-packages) → top-level package prefixes",
      "describe": "(describe \"todo\") or (describe \"todo/item/add\")",
      "package_bindings": "(package-bindings \"todo\") → all names under todo/",
      "package_exports": "(package-exports \"todo\") → shallow pkg/local names",
      "apropos_docs": "(apropos-docs \"todo/item\") → ((name doc) …)",
      "function_doc": "(function-doc \"name\") → docstring | ()",
      "repl_client": "Build client: HTTP Basic + POST /api/eval JSON {expr}. See examples.curl / examples.python; local pp-eval CLI."
    }
  },
  "examples": {
    "curl": "curl -sS -u USER:PASS -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{\"expr\":\"(whoami)\"}' https://pricklypear.rocks/api/eval",
    "python": "import base64, json, urllib.request\nbase = \"https://pricklypear.rocks\"\nuser, password = \"USER\", \"PASS\"\nexpr = \"(whoami)\"\nreq = urllib.request.Request(\n    base.rstrip(\"/\") + \"/api/eval\",\n    data=json.dumps({\"expr\": expr}).encode(),\n    method=\"POST\",\n    headers={\n        \"Content-Type\": \"application/json\",\n        \"Accept\": \"application/json\",\n        \"Authorization\": \"Basic \" + base64.b64encode(\n            f\"{user}:{password}\".encode()).decode(\"ascii\"),\n    },\n)\nprint(urllib.request.urlopen(req).read().decode())\n"
  },
  "related": {
    "welcome": "/welcome",
    "source_browser": "/code",
    "book_pdf": "/book.pdf",
    "source_tarball": "/src.tgz",
    "login": "/login",
    "agent_portal": "/agent (private HTML UI — not this discovery document)"
  }
}