{
  "schema": "snappy-card/1",
  "skill": "snappy-image",
  "tier": "public",
  "origin_tier": "base",
  "purpose": "You are the single producer of every image asset in the Snappy stack. You generate images via Nano Banana 2 (snappy-gemini), capture screenshots (peekaboo, agent-browser, screencapture), resize/convert via sips, composite typography via PIL, and upload finals to the DO Spaces CDN. Copy is always approved BEFORE images get generated -- if upstream copy is not locked, STOP and route to `snappy-content`. Every final asset lands on DO Spaces at a deterministic CDN path; temp URLs never leak into published content.",
  "verbs": [
    {
      "name": "canva",
      "args": [
        "file",
        "folder?"
      ],
      "effect": "write",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts canva <file>"
    },
    {
      "name": "course-folder",
      "args": [
        "course-name"
      ],
      "effect": "write",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts course-folder <course-name>"
    },
    {
      "name": "generate",
      "args": [
        "topic",
        "out"
      ],
      "effect": "write-reversible",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts generate <topic> <out>"
    },
    {
      "name": "logo",
      "args": [
        "tool-name"
      ],
      "effect": "write",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts logo <tool-name>"
    },
    {
      "name": "logos",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts logos"
    },
    {
      "name": "metrics",
      "args": [
        "metric-name"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts metrics <metric-name>"
    },
    {
      "name": "reframe",
      "args": [
        "source"
      ],
      "effect": "write",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts reframe <source>"
    },
    {
      "name": "upload",
      "args": [
        "local-path",
        "remote-path"
      ],
      "effect": "post",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts upload <local-path> <remote-path>"
    },
    {
      "name": "url",
      "args": [
        "remote-path"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-image/api.ts url <remote-path>"
    }
  ],
  "faces": [],
  "agents": [],
  "uses": [
    "snappy-content",
    "snappy-gemini",
    "snappy-ai-models",
    "snappy-browse",
    "snappy-settings",
    "snappy-positioning",
    "snappy-youtube",
    "snappy-linkedin",
    "snappy-blog",
    "snappy-publish",
    "snappy-skool",
    "snappy-course",
    "snappy-ops",
    "snappy-faces"
  ],
  "used_by": [
    "snappy-cleanshot",
    "snappy-course",
    "snappy-ffmpeg",
    "snappy-remotion",
    "snappy-sync"
  ],
  "checks": {
    "pass": 43,
    "fail": 10,
    "defer": 8,
    "line": "43 of 53 checks pass",
    "failing": [
      {
        "id": 4,
        "rule": "Near-neighbour descriptions cross-reference each other",
        "says": "near neighbours not named: snappy-runner, snappy-specwatch"
      },
      {
        "id": 7,
        "rule": "Promised and declared verbs have no set difference",
        "says": "promised-only=[autofill, export]; loader-missing=[]"
      },
      {
        "id": 8,
        "rule": "Skill body and reference graph stay within disclosure budgets",
        "says": "561 lines; ~7899 tokens; whiteboard.md >100 lines without ToC; whiteboard.md >100 lines without ToC; style.md >100 lines without ToC; composite.md >100 lines without ToC"
      },
      {
        "id": 10,
        "rule": "One term names each concept",
        "says": "mixed synonym sets: endpoint/url/route/path, field/box/element"
      },
      {
        "id": 23,
        "rule": "Responses obey hard and soft caps",
        "says": "0 hard-cap breach(es); 1 unannounced default-read soft-cap breach(es)"
      },
      {
        "id": 40,
        "rule": "Metadata, skill body, and resources follow three-level budgets",
        "says": "metadata ~239 tokens; body ~7899 tokens"
      },
      {
        "id": 54,
        "rule": "At least three eval scenarios precede ready status",
        "says": "0 eval scenario(s); ready=true"
      },
      {
        "id": 55,
        "rule": "Eval scenarios are multi-call with ground truth",
        "says": "eval manifest missing"
      },
      {
        "id": 57,
        "rule": "Eval manifest contains a disjoint holdout",
        "says": "0 disjoint holdout scenario(s)"
      },
      {
        "id": 61,
        "rule": "The vendor's own spec still has what this hand offers, and this hand offers what the vendor added",
        "says": "OpenAI added model gpt-image-2.5-sunburst, gpt-image-2.5-sunburst-2026-09-08, gpt-image-2.5-flare, gpt-image-2.5-flare-2026-09-08 and 4 more — this hand still offers dall-e-3, gpt-image-1, gpt-image-2; OpenAI added size 1024x1792, 1024x1536, 1792x1024, 1536x1024 and 3 more — this hand still offers 1024x1024; OpenAI added response_format b64_json — this hand still offers url"
      }
    ]
  },
  "coverage": "covers 1 of 289 operations of OpenAI, checked 2026-09-09",
  "spec": {
    "kind": "openapi",
    "vendor": "OpenAI",
    "version": "2.3.0",
    "url": "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
    "checked": "2026-09-09",
    "covers": 1,
    "of": 289
  },
  "evals": null,
  "files": [
    "AGENTS.md",
    "SKILL.md",
    "api.ts",
    "composite.md",
    "contract.test.ts",
    "course-cover-master-spec.md",
    "data/brands/editorial-flat.json",
    "data/brands/ink-journal.json",
    "data/brands/linear-vercel.json",
    "data/logos/anthropic.png",
    "data/logos/aws.png",
    "data/logos/claude.png",
    "data/logos/cloudflare.png",
    "data/logos/contrast-audit.tsv",
    "data/logos/docker.png",
    "data/logos/fly.png",
    "data/logos/git.png",
    "data/logos/github.png",
    "data/logos/googlecloud.png",
    "data/logos/linear.png",
    "data/logos/neon.png",
    "data/logos/nextjs.png",
    "data/logos/nodejs.png",
    "data/logos/notion.png",
    "data/logos/openai.png",
    "data/logos/playwright.png",
    "data/logos/postgresql.png",
    "data/logos/python.png",
    "data/logos/react.png",
    "data/logos/redis.png",
    "data/logos/slack.png",
    "data/logos/stripe.png",
    "data/logos/supabase.png",
    "data/logos/typescript.png",
    "data/logos/vercel.png",
    "data/logos/xano.png",
    "data/panels/art-photography.json",
    "data/panels/art.json",
    "data/panels/business.json",
    "data/panels/data-journalism.json",
    "data/panels/narrative.json",
    "data/panels/technical-diagram.json",
    "data/panels/textbook.json",
    "data/panels/whimsy.json",
    "data/prompt-learnings.md",
    "entities.json",
    "imageset.test.ts",
    "logos.json",
    "metrics.json",
    "optimization.md",
    "prompting.md",
    "providers.md",
    "rubric.md",
    "scripts/build-logo-registry.sh",
    "scripts/canva-sync.ts",
    "scripts/cdn-upload.sh",
    "scripts/classroom-mosaic.sh",
    "scripts/classroom-retro.sh",
    "scripts/compose-ref.sh",
    "scripts/composite-labels.py",
    "scripts/council.sh",
    "scripts/generate-iterate.sh",
    "scripts/generate.sh",
    "scripts/image-check.py",
    "scripts/refine.sh",
    "scripts/reframe.py",
    "scripts/score-generation.sh",
    "style.md",
    "templates/course-cover.md",
    "templates/course-lesson.md",
    "whiteboard.md",
    "workflows.md"
  ],
  "total_size": 744447,
  "checksum": "sha256:2dc059adefeb1099cb25a059ced96b58848c7f76a989af5cbf4abdfabd11396b",
  "built_at": "2026-09-09T22:09:32.591Z",
  "openapi": {
    "openapi": "3.1.0",
    "info": {
      "title": "snappy-image",
      "version": "0.0.0",
      "description": "Centralized image generation, editing, and capture for Snappy: Nano Banana / Gemini, Grok / xAI edits, gpt-image, Canva via agent-browser, Mac Mini screenshots (peekaboo, screencapture), agent-browser web screenshots, ffmpeg frame extraction, sips resize and conversion, DO Spaces CDN upload; workflows for YouTube thumbnails, LinkedIn images and carousels, Skool visuals, blog heroes, ad creatives, whiteboards and diagrams, dev-update screenshots. Use when Robert says: /snappy-image, 'generate an image using gpt image 2 using the current app as a ref image', 'make thumbnails for youtube', 'screenshot it and do it all', 'take a screenshot of every page and return it', 'make a whiteboard / diagram for the dev update', 'resize / optimize this image', 'upload it to spaces', 'nano banana', 'grok edit'. NOT companion pics (see hot-pic-generator). NOT video or audio (see snappy-video). NOT local screen capture + OCR on this Mac (see snappy-cleanshot)."
    },
    "x-snappy-transport": "cli",
    "servers": [
      {
        "url": "npx tsx ~/.claude/skills/snappy-image/api.ts",
        "description": "The hand itself. Every path is a verb word, every query or body key an argument."
      }
    ],
    "paths": {
      "/canva": {
        "post": {
          "operationId": "snappy_image_canva",
          "summary": "snappy-image canva",
          "x-snappy-effect": "write",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "file": {
                      "type": "string",
                      "description": "Local file uploaded into Canva",
                      "x-snappy-position": 0
                    },
                    "folder": {
                      "type": "string",
                      "description": "Canva folder the upload lands in",
                      "x-snappy-position": 1
                    }
                  },
                  "required": [
                    "file"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/course-folder": {
        "post": {
          "operationId": "snappy_image_course_folder",
          "summary": "snappy-image course-folder",
          "x-snappy-effect": "write",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "course-name": {
                      "type": "string",
                      "description": "Course whose asset folder is created",
                      "x-snappy-position": 0
                    }
                  },
                  "required": [
                    "course-name"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/generate": {
        "post": {
          "operationId": "snappy_image_generate",
          "summary": "snappy-image generate",
          "x-snappy-effect": "write-reversible",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "topic": {
                      "type": "string",
                      "description": "What the generated image is about",
                      "x-snappy-position": 0
                    },
                    "out": {
                      "type": "string",
                      "description": "File path the image is written to",
                      "x-snappy-position": 1
                    },
                    "text": {
                      "x-snappy-flag": "--text"
                    },
                    "illustration": {
                      "x-snappy-flag": "--illustration"
                    },
                    "format": {
                      "x-snappy-flag": "--format"
                    },
                    "brand": {
                      "x-snappy-flag": "--brand"
                    },
                    "ref": {
                      "x-snappy-flag": "--ref"
                    },
                    "json": {
                      "x-snappy-flag": "--json"
                    }
                  },
                  "required": [
                    "topic",
                    "out"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/logo": {
        "post": {
          "operationId": "snappy_image_logo",
          "summary": "snappy-image logo",
          "x-snappy-effect": "write",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tool-name": {
                      "type": "string",
                      "description": "Tool whose logo is fetched or generated",
                      "x-snappy-position": 0
                    }
                  },
                  "required": [
                    "tool-name"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/logos": {
        "get": {
          "operationId": "snappy_image_logos",
          "summary": "snappy-image logos",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "json",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--json"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/metrics": {
        "get": {
          "operationId": "snappy_image_metrics",
          "summary": "snappy-image metrics",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "metric-name",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "Name of the image metric to compute",
                "x-snappy-position": 0
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/reframe": {
        "post": {
          "operationId": "snappy_image_reframe",
          "summary": "snappy-image reframe",
          "x-snappy-effect": "write",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Image reframed to another aspect ratio",
                      "x-snappy-position": 0
                    }
                  },
                  "required": [
                    "source"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/upload": {
        "post": {
          "operationId": "snappy_image_upload",
          "summary": "snappy-image upload",
          "x-snappy-effect": "post",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": true,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "local-path": {
                      "type": "string",
                      "description": "File uploaded to the public CDN",
                      "x-snappy-position": 0
                    },
                    "remote-path": {
                      "type": "string",
                      "description": "Key the file is published under; this URL is world-readable",
                      "x-snappy-position": 1
                    }
                  },
                  "required": [
                    "local-path",
                    "remote-path"
                  ]
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/url": {
        "get": {
          "operationId": "snappy_image_url",
          "summary": "snappy-image url",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "remote-path",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "CDN key whose public URL is returned",
                "x-snappy-position": 0
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      }
    },
    "components": {
      "schemas": {
        "SnappyRefusal": {
          "type": "object",
          "required": [
            "outcome",
            "code",
            "message"
          ],
          "properties": {
            "outcome": {
              "type": "string",
              "const": "refused"
            },
            "code": {
              "type": "string",
              "description": "A row of the closed refusal table."
            },
            "message": {
              "type": "string"
            },
            "fix": {
              "type": "string"
            }
          }
        }
      }
    },
    "x-snappy-requires": [
      "DO_SPACES_KEY",
      "DO_SPACES_SECRET"
    ],
    "x-snappy-vendor-spec": {
      "kind": "openapi",
      "url": "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.json",
      "operations": {
        "generate": "createImage"
      },
      "pinned": {
        "sha256": "8f55897d52dcad8a2321e636af59c4081fabf9809fbc0d42949d4d9ea8461837",
        "checked_at": "2026-09-09T20:44:11Z",
        "version": "2.3.0"
      }
    }
  }
}