diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d220407..7bfc918 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,33 @@ jobs: - name: Run lint run: script/lint + test-snippets: + if: github.event_name == 'push' || !github.event.pull_request.head.repo.fork + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Set up uv + uses: astral-sh/setup-uv@v3 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + - name: Install npm dependencies + run: npm install --silent + working-directory: test + - name: Run snippet tests + env: + REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} + run: python script/test_snippets.py + publish-clawhub: if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: lint + needs: [lint, test-snippets] runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.gitignore b/.gitignore index 7f63c8e..b8f718c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ *.swp *.swo *~ +node_modules/ +__pycache__/ +*.greger diff --git a/AGENTS.md b/AGENTS.md index 67f2277..81996ab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,27 +2,49 @@ ## Purpose -This repo publishes a single Agent Skills document for Replicate. - -Keep it short and focused: a human- and agent-readable guide to discovering models, inspecting schemas, running predictions, and handling outputs. +This repo publishes Agent Skills for Replicate: a main skill document plus focused reference docs covering predictions, model search, collections, workflows, deployments, Cloudflare integration, and the HTTP API. ## Files that matter -- `skills/replicate/SKILL.md` is the canonical skill. -- `.mcp.json` points to the remote MCP server. -- `.claude-plugin/` contains marketplace metadata for Claude Code. +- `skills/replicate/SKILL.md` — the main skill (overview, common patterns, reference table). +- `skills/replicate/references/*.md` — detailed reference docs linked from SKILL.md. +- `script/lint` — validates the skill and lints Python with ruff. +- `script/test_snippets.py` — extracts and runs every code snippet from the markdown files. +- `test/fixtures/` — test assets (e.g. images for workers that accept file uploads). +- `.mcp.json` — points to the remote MCP server. +- `.claude-plugin/` — marketplace metadata for Claude Code. ## Editing guidelines -- Keep `SKILL.md` concise and practical. Prefer bullet lists over long prose. +- Keep `SKILL.md` concise. Detailed examples go in `references/`. +- Every code snippet must be runnable. The test runner executes them all. +- Snippets starting with `// worker.js` or `// workflow.js` are tested via `wrangler dev`. +- Snippets whose worker reads `request.blob()` or `request.arrayBuffer()` get a test image POSTed automatically. - Treat `https://api.replicate.com/openapi.json` as the source of truth. -- Keep mentions of deprecated or unofficial endpoints out of the skill. - Do not add language-specific client guidance unless explicitly requested. ## Linting -Lint before committing changes: - ``` script/lint ``` + +## Testing + +Runs all code snippets (bash, python, javascript) from every markdown file: + +``` +REPLICATE_API_TOKEN=... python script/test_snippets.py +``` + +Syntax check only (no API calls): + +``` +REPLICATE_API_TOKEN=... python script/test_snippets.py --syntax-only +``` + +Test a single reference file: + +``` +REPLICATE_API_TOKEN=... python script/test_snippets.py --include references/CLOUDFLARE_WORKERS.md +``` diff --git a/script/lint b/script/lint index 2347e47..906d2a1 100755 --- a/script/lint +++ b/script/lint @@ -1,10 +1,7 @@ #!/bin/sh -# script/lint: Validate Agent Skills definitions with skills-ref. -# skills-ref is the reference CLI for the Agent Skills spec (agentskills.io). -# It provides the `agentskills` executable with a `validate` subcommand. -# Uses uvx if available for a clean, isolated install of the validator. -# Falls back to a local Python module install if uv is not present. +# script/lint: Validate Agent Skills definitions with skills-ref, +# then lint and format Python scripts with ruff. set -e @@ -18,17 +15,21 @@ echo "==> Validating Agent Skills in $SKILL_PATH" if command -v uv >/dev/null 2>&1; then uvx --from skills-ref agentskills validate "$SKILL_PATH" - exit 0 -fi - -PYTHON_BIN="${PYTHON_BIN:-python3}" -if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then - if command -v python >/dev/null 2>&1; then - PYTHON_BIN=python - else - echo "python not found. Install Python 3 or uv to run skills-ref." >&2 - exit 1 +else + PYTHON_BIN="${PYTHON_BIN:-python3}" + if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then + if command -v python >/dev/null 2>&1; then + PYTHON_BIN=python + else + echo "python not found. Install Python 3 or uv to run skills-ref." >&2 + exit 1 + fi fi + "$PYTHON_BIN" -m skills_ref validate "$SKILL_PATH" fi -"$PYTHON_BIN" -m skills_ref validate "$SKILL_PATH" +echo "==> Linting Python (ruff check)" +uvx ruff check script/test_snippets.py + +echo "==> Formatting Python (ruff format --check)" +uvx ruff format --check script/test_snippets.py diff --git a/script/test_snippets.py b/script/test_snippets.py new file mode 100644 index 0000000..96293fc --- /dev/null +++ b/script/test_snippets.py @@ -0,0 +1,495 @@ +#!/usr/bin/env python +""" +Test all code snippets extracted from the skill markdown files. + +Parses fenced code blocks from every .md file under skills/replicate/, +then runs each one in parallel (8 workers). Reports pass/fail per snippet. + +Snippets starting with ``// worker.js`` or ``// workflow.js`` are run inside +a temporary Wrangler project (``wrangler dev``) and exercised via HTTP. + +Usage: + REPLICATE_API_TOKEN=... python script/test_snippets.py + REPLICATE_API_TOKEN=... python script/test_snippets.py --syntax-only + REPLICATE_API_TOKEN=... python script/test_snippets.py --include references/PREDICTIONS.md + REPLICATE_API_TOKEN=... python script/test_snippets.py --exclude references/DEPLOYMENTS.md +""" + +import argparse +import json +import os +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import threading +import urllib.error +import urllib.request +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass +from pathlib import Path + +ROOT_DIR = Path(__file__).resolve().parent.parent +SKILL_DIR = ROOT_DIR / "skills" / "replicate" +TEST_DIR = ROOT_DIR / "test" + +DEFAULT_EXCLUDE = ["references/DEPLOYMENTS.md"] + +PIPELINE_MARKERS: list[str] = [] + +NODE_MODULES_DIR = TEST_DIR / "node_modules" + +MAX_WORKERS = 8 +WRANGLER_CONCURRENCY = 1 + +_wrangler_semaphore = threading.Semaphore(WRANGLER_CONCURRENCY) + + +@dataclass +class Snippet: + file: str + index: int + lang: str + code: str + line: int + + @property + def label(self): + return f"{self.file}:{self.line} [{self.lang} #{self.index}]" + + def is_pipeline_only(self): + return any(m in self.code for m in PIPELINE_MARKERS) + + @property + def wrangler_kind(self): + first_line = self.code.split("\n", 1)[0].strip() + if first_line == "// worker.js": + return "worker" + if first_line == "// workflow.js": + return "workflow" + return None + + +def read_wrangler_port(proc: subprocess.Popen, timeout: int = 60) -> int: + """Read wrangler's stdout in a thread until we find 'Ready on http://...:PORT'.""" + result = {"port": None, "output": b""} + ready_event = threading.Event() + + def reader(): + while True: + line = proc.stdout.readline() + if not line: + break + result["output"] += line + m = re.search(rb"Ready on http://localhost:(\d+)", line) + if m: + result["port"] = int(m.group(1)) + ready_event.set() + break + + t = threading.Thread(target=reader, daemon=True) + t.start() + + if not ready_event.wait(timeout): + raise RuntimeError( + f"wrangler dev did not become ready within {timeout}s\n" + + result["output"].decode(errors="replace") + ) + return result["port"] + + +def extract_snippets(md_path: Path, relative: str) -> list[Snippet]: + text = md_path.read_text() + snippets = [] + pattern = re.compile(r"^```(\w+)\n(.*?)^```", re.MULTILINE | re.DOTALL) + for i, m in enumerate(pattern.finditer(text)): + lang = m.group(1) + code = m.group(2) + line = text[: m.start()].count("\n") + 1 + if lang in ("bash", "python", "javascript"): + snippets.append( + Snippet(file=relative, index=i, lang=lang, code=code, line=line) + ) + return snippets + + +# ---------- plain snippet runners ---------- + + +def check_python_syntax(snippet: Snippet): + compile(snippet.code, snippet.label, "exec") + + +def run_python(snippet: Snippet) -> subprocess.CompletedProcess: + with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f: + f.write(snippet.code) + f.flush() + try: + return subprocess.run( + ["uvx", "--python", "3.12", "--with", "replicate", "python", f.name], + capture_output=True, + text=True, + timeout=120, + env={**os.environ}, + ) + finally: + os.unlink(f.name) + + +def check_js_syntax(snippet: Snippet): + with tempfile.NamedTemporaryFile(suffix=".mjs", mode="w", delete=False) as f: + f.write(snippet.code) + f.flush() + try: + result = subprocess.run( + ["node", "--check", f.name], + capture_output=True, + text=True, + timeout=10, + ) + if result.returncode != 0: + raise SyntaxError(result.stderr.strip()) + finally: + os.unlink(f.name) + + +def run_js(snippet: Snippet) -> subprocess.CompletedProcess: + code = snippet.code + wrapped = f"(async () => {{\n{code}\n}})().catch(e => {{ console.error(e); process.exit(1); }});\n" + + with tempfile.NamedTemporaryFile( + suffix=".cjs", mode="w", delete=False, dir=str(NODE_MODULES_DIR.parent) + ) as f: + f.write(wrapped) + f.flush() + try: + return subprocess.run( + ["node", f.name], + capture_output=True, + text=True, + timeout=120, + env={**os.environ}, + cwd=str(NODE_MODULES_DIR.parent), + ) + finally: + os.unlink(f.name) + + +def run_bash(snippet: Snippet) -> subprocess.CompletedProcess: + return subprocess.run( + ["bash", "-e", "-c", snippet.code], + capture_output=True, + text=True, + timeout=120, + env={**os.environ}, + ) + + +# ---------- wrangler runner ---------- + + +def extract_workflow_class_names(code: str) -> list[str]: + return re.findall(r"export\s+class\s+(\w+)\s+extends\s+WorkflowEntrypoint", code) + + +def scaffold_wrangler_project(tmpdir: Path, snippet: Snippet): + (tmpdir / "src").mkdir() + (tmpdir / "src" / "index.js").write_text(snippet.code) + + (tmpdir / "package.json").write_text( + json.dumps( + { + "name": "snippet-test", + "private": True, + "type": "module", + } + ) + ) + + wrangler_config = { + "name": "snippet-test", + "main": "src/index.js", + "compatibility_date": "2025-01-01", + "compatibility_flags": ["nodejs_compat"], + } + + if snippet.wrangler_kind == "workflow": + class_names = extract_workflow_class_names(snippet.code) + wrangler_config["workflows"] = [ + { + "name": f"wf-{cls.lower()}", + "binding": binding_name_for_class(cls, snippet.code), + "class_name": cls, + } + for cls in class_names + ] + + (tmpdir / "wrangler.jsonc").write_text(json.dumps(wrangler_config, indent=2)) + os.symlink(str(NODE_MODULES_DIR), str(tmpdir / "node_modules")) + + +def binding_name_for_class(class_name: str, code: str): + for m in re.finditer(r"env\.(\w+)\.\w+\(", code): + binding = m.group(1) + if binding != "REPLICATE_API_TOKEN" and binding.isupper(): + return binding + return re.sub(r"(?= 500: + raise RuntimeError(f"Worker returned HTTP {status}: {body[:300]}") + + +def exercise_workflow(port: int): + url = f"http://localhost:{port}/" + req = urllib.request.Request(url, method="GET") + resp = urllib.request.urlopen(req, timeout=120) + status = resp.status + if status != 200: + body = resp.read().decode() + raise RuntimeError(f"Workflow trigger returned HTTP {status}: {body[:300]}") + + +# ---------- setup ---------- + + +def ensure_node_modules(): + pkg_json = TEST_DIR / "package.json" + current = json.loads(pkg_json.read_text()) if pkg_json.exists() else {} + deps = current.get("dependencies", {}) + needs_install = not NODE_MODULES_DIR.exists() or "wrangler" not in deps + + if needs_install: + print("Installing npm packages (replicate + wrangler)...") + pkg_json.write_text( + json.dumps( + { + "name": "test", + "private": True, + "type": "commonjs", + "dependencies": { + "replicate": "^1.4.0", + "wrangler": "^4.0.0", + }, + }, + indent=2, + ) + + "\n" + ) + subprocess.run( + ["npm", "install", "--silent"], + cwd=str(TEST_DIR), + check=True, + capture_output=True, + ) + elif not NODE_MODULES_DIR.exists(): + print("Installing npm packages...") + subprocess.run( + ["npm", "install", "--silent"], + cwd=str(TEST_DIR), + check=True, + capture_output=True, + ) + + +# ---------- run a single snippet (called from thread pool) ---------- + + +def run_one(snippet: Snippet, syntax_only: bool) -> tuple[Snippet, str | None]: + """Return (snippet, None) on success or (snippet, error_message) on failure.""" + if snippet.lang == "python": + check_python_syntax(snippet) + elif snippet.lang == "javascript" and not snippet.wrangler_kind: + check_js_syntax(snippet) + + if syntax_only: + return snippet, None + + if snippet.wrangler_kind: + with _wrangler_semaphore: + run_wrangler_snippet(snippet) + return snippet, None + + if snippet.lang == "python": + result = run_python(snippet) + elif snippet.lang == "javascript": + result = run_js(snippet) + elif snippet.lang == "bash": + result = run_bash(snippet) + else: + return snippet, None + + if result.returncode != 0: + err = result.stderr.strip() or result.stdout.strip() + if len(err) > 500: + err = err[:500] + "..." + return snippet, f"exit {result.returncode}: {err}" + + return snippet, None + + +# ---------- main ---------- + + +def main(): + parser = argparse.ArgumentParser( + description="Test code snippets in skill markdown files" + ) + parser.add_argument( + "--syntax-only", action="store_true", help="Only check syntax, don't execute" + ) + parser.add_argument( + "--include", + action="append", + default=[], + help="Only test these files (relative to skill dir)", + ) + parser.add_argument( + "--exclude", + action="append", + default=[], + help="Skip these files (relative to skill dir)", + ) + args = parser.parse_args() + + if not os.environ.get("REPLICATE_API_TOKEN"): + print("REPLICATE_API_TOKEN not set", file=sys.stderr) + sys.exit(1) + + excludes = set(args.exclude or DEFAULT_EXCLUDE) + + md_files = sorted(SKILL_DIR.rglob("*.md")) + all_snippets = [] + for md_path in md_files: + relative = str(md_path.relative_to(SKILL_DIR)) + if args.include and relative not in args.include: + continue + if relative in excludes: + continue + all_snippets.extend(extract_snippets(md_path, relative)) + + if not all_snippets: + print("No snippets found!") + sys.exit(1) + + if not args.syntax_only: + ensure_node_modules() + + passed = [] + failed = [] + skipped = [] + + # Separate pipeline-only snippets (skipped) from runnable ones + runnable = [] + for snippet in all_snippets: + if snippet.is_pipeline_only(): + skipped.append((snippet, "pipeline-only")) + print(f" SKIP {snippet.label} (pipeline-only)") + else: + runnable.append(snippet) + + print_lock = threading.Lock() + + with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool: + future_to_snippet = { + pool.submit(run_one, snippet, args.syntax_only): snippet + for snippet in runnable + } + for future in as_completed(future_to_snippet): + snippet = future_to_snippet[future] + try: + _, err = future.result() + except Exception as e: + err = str(e) + + with print_lock: + if err is None: + passed.append(snippet) + suffix = " (syntax)" if args.syntax_only else "" + print(f" OK {snippet.label}{suffix}") + else: + failed.append((snippet, err)) + print(f" FAIL {snippet.label}") + for line in err.split("\n")[:5]: + print(f" {line}") + + print() + print( + f"Results: {len(passed)} passed, {len(failed)} failed, {len(skipped)} skipped" + ) + + if failed: + print("\nFailed snippets:") + for snippet, err in failed: + print(f" {snippet.label}") + for line in err.split("\n")[:3]: + print(f" {line}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/replicate/SKILL.md b/skills/replicate/SKILL.md index 0960d34..2e31c47 100644 --- a/skills/replicate/SKILL.md +++ b/skills/replicate/SKILL.md @@ -1,56 +1,181 @@ --- name: replicate -description: Discover, compare, and run AI models using Replicate's API +description: > + Run AI models on Replicate. Use when building apps that generate images, video, speech, + music, or text, or when the user asks about Replicate models, predictions, or deployments. + Covers model search, schema inspection, running predictions, collections, deployments, + and multi-model pipelines. --- +# Replicate + +Replicate lets you run AI models with a cloud API. Thousands of models for image generation, video, speech, music, text, and more. + ## Docs -- Reference docs: https://replicate.com/docs/llms.txt -- HTTP API schema: https://api.replicate.com/openapi.json -- MCP server: https://mcp.replicate.com -- Set an `Accept: text/markdown` header when requesting docs pages to get a Markdown response. +- Reference: +- OpenAPI schema: +- MCP server: +- Per-model docs: `https://replicate.com/{owner}/{model}/llms.txt` +- Set `Accept: text/markdown` when requesting docs pages for Markdown responses. + +## Key concepts + +- **Models** are identified as `owner/name` (e.g. `black-forest-labs/flux-2-klein-9b`). +- **Official models** are always warm, have stable APIs, and predictable pricing. Use `owner/name`. +- **Community models** require a version: `owner/name:version_id`. They cold-boot and can be slow. +- **Predictions** are individual model runs: `starting` → `processing` → `succeeded`/`failed`/`canceled`. +- **Deployments** are always-on instances of community models you manage. +- **Collections** are curated groups of models (e.g. `text-to-image`, `official`). + +## Search for models + +Use the search API to find models by task. It returns models, collections, and docs. + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + "https://api.replicate.com/v1/search?query=image+generation" | jq '[.models[:3][] | {name: .model.name, owner: .model.owner}]' +``` + +```python +import replicate + +page = replicate.models.search("image generation") +for model in page.results[:3]: + print(f"{model.owner}/{model.name}: {model.description}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const page = await replicate.models.search("image generation"); +for (const model of page.results.slice(0, 3)) { + console.log(`${model.owner}/${model.name}: ${model.description}`); +} +``` + +For deeper guidance on picking the right model, see [MODEL_SEARCH.md](references/MODEL_SEARCH.md). + +## Get a model's schema + +Always fetch a model's schema before running it. Schemas change. + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/models/black-forest-labs/flux-2-klein-9b \ + | jq '.latest_version.openapi_schema.components.schemas.Input.properties | keys' +``` + +```python +import replicate + +model = replicate.models.get("black-forest-labs", "flux-schnell") +schema = model.latest_version.openapi_schema["components"]["schemas"]["Input"]["properties"] +for name, prop in schema.items(): + print(f" {name}: {prop.get('type', '?')} — {prop.get('description', '')}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const model = await replicate.models.get("black-forest-labs", "flux-schnell"); +const schema = + model.latest_version.openapi_schema.components.schemas.Input.properties; +for (const [name, prop] of Object.entries(schema)) { + console.log(` ${name}: ${prop.type || "?"} — ${prop.description || ""}`); +} +``` + +## Run a prediction + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Prefer: wait=60" \ + -d '{"version": "black-forest-labs/flux-2-klein-9b", "input": {"prompt": "a cat wearing a top hat", "num_outputs": 1}}' \ + https://api.replicate.com/v1/predictions | jq '{id, status, output}' +``` + +```python +import replicate + +output = replicate.run( + "black-forest-labs/flux-2-klein-9b", + input={"prompt": "a cat wearing a top hat", "num_outputs": 1}, +) +for item in output: + print(item.url) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const output = await replicate.run("black-forest-labs/flux-2-klein-9b", { + input: { prompt: "a cat wearing a top hat", num_outputs: 1 }, +}); +console.log(output); +``` + +For polling, webhooks, streaming, file I/O, and concurrency patterns, see [PREDICTIONS.md](references/PREDICTIONS.md). + +## Key rules + +- **Always fetch the schema first.** Even popular models change their interfaces. +- **Validate inputs against schema constraints** — check `minimum`, `maximum`, `enum` values. +- **Don't set optional inputs without reason.** Let the model's defaults work. +- **Prefer official models.** They're warm, stable, and predictably priced. +- **Use HTTPS URLs for file inputs.** Base64 works but is slower. +- **Run predictions concurrently.** Don't wait for one to finish before starting the next. +- **Output file URLs expire in 1 hour.** Back them up to R2/S3 if you need to keep them. -## Workflow +## Browse collections -Here's a common workflow for using Replicate's API to run a model: +Collections are curated model groups maintained by Replicate. The `official` collection has always-warm models. -1. **Choose the right model** - Search with the API or ask the user -2. **Get model metadata** - Fetch model input and output schema via API -3. **Create prediction** - POST to /v1/predictions -4. **Poll for results** - GET prediction until status is "succeeded" -5. **Return output** - Usually URLs to generated content +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/collections/official | jq '{name, slug, description}' +``` -## Choosing models +```python +import replicate -- Use the search and collections APIs to find and compare the best models. Do not list all the models via API, as it's basically a firehose. -- Collections are curated by Replicate staff, so they're vetted. -- Official models are in the "official" collection. -- Use official models because they: - - are always running - - have stable API interfaces - - have predictable output pricing - - are maintained by Replicate staff -- If you must use a community model, be aware that it can take a long time to boot. -- You can create always-on deployments of community models, but you pay for model uptime. +collection = replicate.collections.get("official") +print(f"{collection.name}: {collection.description}") +for model in collection.models[:3]: + print(f" {model.owner}/{model.name}") +``` -## Running models +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); -Models take time to run. There are three ways to run a model via API and get its output: +const collection = await replicate.collections.get("official"); +console.log(`${collection.name}: ${collection.description}`); +for (const model of collection.models.slice(0, 3)) { + console.log(` ${model.owner}/${model.name}`); +} +``` -1. Create a prediction, store its id from the response, and poll until completion. -2. Set a `Prefer: wait` header when creating a prediction for a blocking synchronous response. Only recommended for very fast models. -3. Set an HTTPS webhook URL when creating a prediction, and Replicate will POST to that URL when the prediction completes. +For the full collection list and API details, see [COLLECTIONS.md](references/COLLECTIONS.md). -Follow these guideliness when running models: +## Multi-model workflows -- Use the "POST /v1/predictions" endpoint, as it supports both official and community models. -- Every model has its own OpenAPI schema. Always fetch and check model schemas to make sure you're setting valid inputs. Even popular models change their schemas. -- Validate input parameters against schema constraints (minimum, maximum, enum values). Don't generate values that violate them. -- When unsure about a parameter value, use the model's default example or omit the optional parameter. -- Don't set optional inputs unless you have a reason to. Stick to the required inputs and let the model's defaults do the work. -- Use HTTPS URLs for file inputs whenever possible. You can also send base64-encoded files, but they should be avoided. -- Fire off multiple predictions concurrently. Don't wait for one to finish before starting the next. -- Output file URLs expire after 1 hour, so back them up if you need to keep them, using a service like Cloudflare R2. -- Webhooks are a good mechanism for receiving and storing prediction output. +Complex tasks often chain multiple models. Use parallel predictions for speed. See [WORKFLOWS.md](references/WORKFLOWS.md) for pipeline patterns including video generation, image editing, and audio translation. +## References +| Reference | Content | +|-----------|---------| +| [HTTP_API.md](references/HTTP_API.md) | Full HTTP API reference — endpoints, auth, pagination, errors | +| [MODEL_SEARCH.md](references/MODEL_SEARCH.md) | Finding models, reading schemas, picking the right model | +| [COLLECTIONS.md](references/COLLECTIONS.md) | Browsing curated model collections, full collection list | +| [PREDICTIONS.md](references/PREDICTIONS.md) | Running models — official vs community, polling, webhooks, files, concurrency | +| [DEPLOYMENTS.md](references/DEPLOYMENTS.md) | Custom always-on deployments for community models | +| [WORKFLOWS.md](references/WORKFLOWS.md) | Multi-model pipeline patterns — video, image editing, audio, chaining | +| [CLOUDFLARE_WORKERS.md](references/CLOUDFLARE_WORKERS.md) | Using Replicate from Cloudflare Workers | +| [CLOUDFLARE_WORKFLOWS.md](references/CLOUDFLARE_WORKFLOWS.md) | Multi-step Replicate pipelines with Cloudflare Workflows | diff --git a/skills/replicate/references/CLOUDFLARE_WORKERS.md b/skills/replicate/references/CLOUDFLARE_WORKERS.md new file mode 100644 index 0000000..2daab56 --- /dev/null +++ b/skills/replicate/references/CLOUDFLARE_WORKERS.md @@ -0,0 +1,178 @@ +# Replicate in Cloudflare Workers + +Run Replicate models from Cloudflare Workers using the `replicate` npm package. Workers provide sub-millisecond cold starts and a global edge network — pair them with Replicate for AI inference without managing GPU infrastructure. + +## Setup + +**Install dependencies:** + +``` +npm install replicate wrangler +``` + +**Set `nodejs_compat`** in `wrangler.jsonc` — the `replicate` package needs it: + +```jsonc +{ + "name": "my-replicate-worker", + "main": "src/index.js", + "compatibility_date": "2025-01-01", + "compatibility_flags": ["nodejs_compat"] +} +``` + +**Set your API token as a secret** (never put it in code or config): + +``` +npx wrangler secret put REPLICATE_API_TOKEN +``` + +For local dev, pass it as a var: `npx wrangler dev --var REPLICATE_API_TOKEN:r8_...` + +## Basic prediction + +A Worker that generates an image with Pruna P-Image and returns the output URL. + +```javascript +// worker.js +import Replicate from "replicate"; + +export default { + async fetch(request, env) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + const output = await replicate.run("prunaai/p-image", { + input: { prompt: "a red panda in a bamboo forest" }, + }); + return Response.json({ url: output.url() }); + }, +}; +``` + +## Async create + poll + +For long-running models, create the prediction and poll for completion. This avoids the 60-second sync timeout. + +```javascript +// worker.js +import Replicate from "replicate"; + +export default { + async fetch(request, env) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + + let prediction = await replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a red panda in a bamboo forest", aspect_ratio: "16:9" }, + }); + + while (!["succeeded", "failed", "canceled"].includes(prediction.status)) { + await new Promise((r) => setTimeout(r, 1000)); + prediction = await replicate.predictions.get(prediction.id); + } + + return Response.json({ + id: prediction.id, + status: prediction.status, + output: prediction.output, + }); + }, +}; +``` + +## Concurrent predictions + +Fire off multiple predictions in parallel. Workers handle concurrent `fetch` calls well. + +```javascript +// worker.js +import Replicate from "replicate"; + +export default { + async fetch(request, env) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + + const prompts = [ + "a red panda eating bamboo", + "a blue parrot riding a bicycle", + "a green iguana playing chess", + ]; + + const outputs = await Promise.all( + prompts.map((prompt) => + replicate.run("black-forest-labs/flux-2-klein-9b", { + input: { prompt, num_outputs: 1 }, + }), + ), + ); + + const urls = outputs.map((output) => output[0].url); + return Response.json({ urls }); + }, +}; +``` + +## Image editing with user input + +Accept a raw image POST and transform it using Qwen Image Edit. The request body is the image bytes — pass them to the model as a `Blob`. + +```javascript +// worker.js +import Replicate from "replicate"; + +export default { + async fetch(request, env) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + const image = await request.blob(); + + const output = await replicate.run("qwen/qwen-image-edit-plus", { + input: { + image: [image], + prompt: "Turn this image into lego", + aspect_ratio: "match_input_image", + output_format: "webp", + output_quality: 95, + }, + }); + + return Response.json({ url: output[0].url() }); + }, +}; +``` + +## Routing by path + +A single Worker that handles different model tasks based on the URL path. + +```javascript +// worker.js +import Replicate from "replicate"; + +export default { + async fetch(request, env) { + const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); + const url = new URL(request.url); + + if (url.pathname === "/generate" && request.method === "POST") { + const body = await request.json(); + const output = await replicate.run("prunaai/p-image", { + input: { prompt: body.prompt }, + }); + return Response.json({ url: output.url() }); + } + + if (url.pathname === "/caption" && request.method === "POST") { + const body = await request.json(); + const output = await replicate.run( + "andreasjansson/blip-2:f677695e5e89f8b236e52ecd1d3f01beb44c34606419bcc19345e046d8f786f9", + { input: { image: body.image_url, question: "What is in this image?" } }, + ); + return Response.json({ caption: output }); + } + + return Response.json( + { error: "not found" }, + { status: 404 }, + ); + }, +}; +``` diff --git a/skills/replicate/references/CLOUDFLARE_WORKFLOWS.md b/skills/replicate/references/CLOUDFLARE_WORKFLOWS.md new file mode 100644 index 0000000..1381e5c --- /dev/null +++ b/skills/replicate/references/CLOUDFLARE_WORKFLOWS.md @@ -0,0 +1,111 @@ +# Replicate in Cloudflare Workflows + +Use Cloudflare Workflows to orchestrate multi-step Replicate pipelines with automatic retries and state persistence. Each Replicate API call goes in its own `step.do()` — if it fails, only that step retries, not the whole pipeline. + +## Setup + +**Install dependencies:** + +``` +npm install replicate wrangler +``` + +**`wrangler.jsonc`** — bind the workflow and enable `nodejs_compat`: + +```jsonc +{ + "name": "my-replicate-workflow", + "main": "src/index.js", + "compatibility_date": "2025-01-01", + "compatibility_flags": ["nodejs_compat"], + "workflows": [ + { "name": "replicate-pipeline", "binding": "PIPELINE", "class_name": "ReplicatePipeline" } + ] +} +``` + +**Set your API token as a secret:** + +``` +npx wrangler secret put REPLICATE_API_TOKEN +``` + +For local dev: `npx wrangler dev --var REPLICATE_API_TOKEN:r8_...` + +## Video pipeline: keyframes → interpolation → stitching + +Generate three keyframe images in parallel with Flux 2 Klein, feed consecutive pairs as start/end frames to Wan 2.2 I2V to create video segments in parallel, then stitch the segments into a single video. + +The Worker's `fetch` handler triggers the workflow. The workflow class contains the durable pipeline logic. + +```javascript +// workflow.js +import { WorkflowEntrypoint } from "cloudflare:workers"; +import Replicate from "replicate"; + +export class ReplicatePipeline extends WorkflowEntrypoint { + async run(event, step) { + const replicate = new Replicate({ auth: this.env.REPLICATE_API_TOKEN }); + const prompts = event.payload.prompts; + + // Step 1: Generate three keyframe images in parallel. + // Each step.do is independently retriable — if one keyframe fails, + // the others don't re-run. + const keyframes = await Promise.all( + prompts.map((prompt, i) => + step.do(`keyframe-${i}`, { retries: { limit: 3, delay: "5 seconds", backoff: "exponential" } }, async () => { + const output = await replicate.run("black-forest-labs/flux-2-klein-9b", { + input: { prompt, aspect_ratio: "16:9" }, + }); + return output.url(); + }), + ), + ); + + // Step 2: Generate video segments between consecutive keyframes in parallel. + // keyframes[0]→[1] and keyframes[1]→[2] run at the same time. + const segments = await Promise.all( + keyframes.slice(0, -1).map((startFrame, i) => + step.do(`video-segment-${i}`, { retries: { limit: 3, delay: "10 seconds", backoff: "exponential" }, timeout: "10 minutes" }, async () => { + const output = await replicate.run("wan-video/wan-2.2-i2v-fast", { + input: { + prompt: prompts[i], + image: startFrame, + last_image: keyframes[i + 1], + num_frames: 81, + }, + }); + return output.url(); + }), + ), + ); + + // Step 3: Stitch the video segments into a single continuous video. + const stitched = await step.do("stitch-videos", { retries: { limit: 3, delay: "10 seconds", backoff: "exponential" }, timeout: "5 minutes" }, async () => { + const output = await replicate.run( + "andreasjansson/video-stitcher:11365b52712fbf76932e83bfef43a7ccb1af898fbefcd3da00ecea25d2a40f5e", + { input: { videos: segments, overlap_seconds: 0.5 } }, + ); + return output.url(); + }); + + return { keyframes, segments, stitched }; + } +} + +export default { + async fetch(request, env) { + const instance = await env.PIPELINE.create({ + params: { + prompts: [ + "a serene mountain lake at sunrise, cinematic", + "the sun rising higher over the mountain lake, golden light", + "bright midday sun over the mountain lake, vivid colors", + ], + }, + }); + + return Response.json({ id: instance.id, status: "started" }); + }, +}; +``` diff --git a/skills/replicate/references/COLLECTIONS.md b/skills/replicate/references/COLLECTIONS.md new file mode 100644 index 0000000..70aecb5 --- /dev/null +++ b/skills/replicate/references/COLLECTIONS.md @@ -0,0 +1,76 @@ +# Collections + +Collections are curated groups of models maintained by Replicate staff. They're a good way to discover vetted models for specific tasks. + +## List all collections + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/collections | jq '[.results[] | {slug, name}]' +``` + +```python +import replicate + +page = replicate.collections.list() +for collection in page.results: + print(f"{collection.slug}: {collection.name} — {collection.description}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const page = await replicate.collections.list(); +for (const collection of page.results) { + console.log( + `${collection.slug}: ${collection.name} — ${collection.description}`, + ); +} +``` + +## Get a collection + +Returns the collection metadata and a list of its models: + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/collections/text-to-image | jq '{name, slug, description, model_count: (.models | length)}' +``` + +```python +import replicate + +collection = replicate.collections.get("text-to-image") +print(f"{collection.name}: {collection.description}") +for model in collection.models[:3]: + print(f" {model.owner}/{model.name}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const collection = await replicate.collections.get("text-to-image"); +console.log(`${collection.name}: ${collection.description}`); +for (const model of collection.models.slice(0, 3)) { + console.log(` ${model.owner}/${model.name}`); +} +``` + +The response includes `full_description` (Markdown) and a `models` array with full model objects. + +## The `official` collection + +The `official` collection contains models that are always warm, have stable APIs, and predictable per-run pricing. Always prefer official models when available. + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/collections/official | jq '{name, model_count: (.models | length)}' +``` + +## Available collections + +To see the current list of collections, use the API to [list all collections](#list-all-collections). + +The search API also returns matching collections alongside model results. diff --git a/skills/replicate/references/DEPLOYMENTS.md b/skills/replicate/references/DEPLOYMENTS.md new file mode 100644 index 0000000..b704c51 --- /dev/null +++ b/skills/replicate/references/DEPLOYMENTS.md @@ -0,0 +1,202 @@ +# Deployments + +A deployment is an always-on, private instance of a model version. You configure the hardware, minimum and maximum number of instances, and get a fixed API endpoint. You pay for the uptime of the instances (not per-prediction). + +Use deployments when: +- A community model cold-boots too slowly for your use case +- You need a private, stable endpoint for a specific model version +- You want custom scaling (e.g. always keep 2 workers warm) + +Official models are already always-warm — you don't need deployments for them. + +## Create a deployment + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "my-deployment", + "model": "black-forest-labs/flux-dev", + "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + "hardware": "gpu-a40-large", + "min_instances": 1, + "max_instances": 5 + }' \ + https://api.replicate.com/v1/deployments +``` + +```python +import replicate + +deployment = replicate.deployments.create( + name="my-deployment", + model="black-forest-labs/flux-dev", + version="5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + hardware="gpu-a40-large", + min_instances=1, + max_instances=5, +) +print(deployment.name) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const deployment = await replicate.deployments.create({ + name: "my-deployment", + model: "black-forest-labs/flux-dev", + version: + "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", + hardware: "gpu-a40-large", + min_instances: 1, + max_instances: 5, +}); +console.log(deployment.name); +``` + +## List deployments + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/deployments | jq '[.results[] | {name: .name}]' +``` + +```python +import replicate + +page = replicate.deployments.list() +for d in page.results: + print(d.name) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const page = await replicate.deployments.list(); +for (const d of page.results) { + console.log(d.name); +} +``` + +## Get a deployment + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/deployments/your-org/my-deployment | jq '{name}' +``` + +```python +import replicate + +deployment = replicate.deployments.get("your-org/my-deployment") +print(deployment.name) +print(deployment.current_release) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const deployment = await replicate.deployments.get("your-org/my-deployment"); +console.log(deployment.name); +console.log(deployment.current_release); +``` + +## Update a deployment + +```bash +curl -s -X PATCH \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"min_instances": 2, "max_instances": 10}' \ + https://api.replicate.com/v1/deployments/your-org/my-deployment +``` + +```python +import replicate + +deployment = replicate.deployments.update( + "your-org", + "my-deployment", + min_instances=2, + max_instances=10, +) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const deployment = await replicate.deployments.update("your-org/my-deployment", { + min_instances: 2, + max_instances: 10, +}); +``` + +## Run a prediction against a deployment + +Use the deployments predictions endpoint instead of the regular predictions endpoint: + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Prefer: wait=60" \ + -d '{"input": {"prompt": "a red panda in a bamboo forest"}}' \ + https://api.replicate.com/v1/deployments/your-org/my-deployment/predictions +``` + +```python +import replicate + +deployment = replicate.deployments.get("your-org/my-deployment") +prediction = deployment.predictions.create( + input={"prompt": "a red panda in a bamboo forest"}, +) +print(prediction.id, prediction.status) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const prediction = await replicate.deployments.predictions.create( + "your-org", + "my-deployment", + { input: { prompt: "a red panda in a bamboo forest" } }, +); +console.log(prediction.id, prediction.status); +``` + +## Available hardware + +Get available hardware options from the hardware endpoint: + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/hardware | jq '[.[] | {name, sku}]' +``` + +## Delete a deployment + +```bash +curl -s -X DELETE \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/deployments/your-org/my-deployment +``` + +```python +import replicate + +replicate.deployments.delete("your-org", "my-deployment") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +await replicate.deployments.delete("your-org/my-deployment"); +``` diff --git a/skills/replicate/references/HTTP_API.md b/skills/replicate/references/HTTP_API.md new file mode 100644 index 0000000..88a8ea3 --- /dev/null +++ b/skills/replicate/references/HTTP_API.md @@ -0,0 +1,195 @@ +# HTTP API Reference + +## Authentication + +All requests require a Bearer token in the `Authorization` header. + +``` +Authorization: Bearer r8_your_token_here +``` + +Get tokens at . + +## Base URL + +``` +https://api.replicate.com/v1 +``` + +## Endpoints + +### Search + +``` +GET /v1/search?query={query} +``` + +Returns matching models, collections, and docs. Each model result includes metadata like `tags`, `generated_description`, and `run_count`. The search API is in beta. + +### Collections + +``` +GET /v1/collections +GET /v1/collections/{collection_slug} +``` + +List all curated collections, or get one by slug with its models. + +### Models + +``` +GET /v1/models/{owner}/{name} +GET /v1/models/{owner}/{name}/versions +GET /v1/models/{owner}/{name}/versions/{version_id} +GET /v1/models/{owner}/{name}/readme +GET /v1/models/{owner}/{name}/examples +``` + +The model response includes `latest_version.openapi_schema` with full input/output schemas. + +### Predictions + +``` +POST /v1/predictions # Works for all models +POST /v1/models/{owner}/{name}/predictions # Official models only +POST /v1/deployments/{owner}/{name}/predictions # Deployments only +GET /v1/predictions/{id} # Poll for result +POST /v1/predictions/{id}/cancel # Cancel a running prediction +GET /v1/predictions # List your predictions +``` + +The unified `POST /v1/predictions` endpoint accepts these `version` formats: +- `owner/name` — official models (e.g. `black-forest-labs/flux-2-klein-9b`) +- `owner/name:version_id` — community models with pinned version +- `version_id` — raw 64-character version hash + +### Deployments + +``` +GET /v1/deployments +POST /v1/deployments +GET /v1/deployments/{owner}/{name} +PATCH /v1/deployments/{owner}/{name} +DELETE /v1/deployments/{owner}/{name} +POST /v1/deployments/{owner}/{name}/predictions +``` + +### Other + +``` +GET /v1/account # Current user info +GET /v1/hardware # Available hardware options +POST /v1/files # Upload a file +GET /v1/files/{id} # Get file metadata +DELETE /v1/files/{id} # Delete a file +``` + +## Request/response format + +### Creating a prediction + +Request: + +```json +{ + "version": "black-forest-labs/flux-2-klein-9b", + "input": { + "prompt": "a cat wearing a top hat" + }, + "webhook": "https://example.com/webhook", + "webhook_events_filter": ["completed"], + "lifetime": "5m" +} +``` + +Response: + +```json +{ + "id": "gm3qorzdhgbfurvjtvhg6dckhu", + "model": "black-forest-labs/flux-2-klein-9b", + "version": "...", + "input": {"prompt": "a cat wearing a top hat"}, + "output": null, + "error": null, + "logs": "", + "status": "starting", + "created_at": "2024-01-01T00:00:00.000Z", + "started_at": null, + "completed_at": null, + "metrics": {}, + "urls": { + "get": "https://api.replicate.com/v1/predictions/gm3qorzdhgbfurvjtvhg6dckhu", + "cancel": "https://api.replicate.com/v1/predictions/gm3qorzdhgbfurvjtvhg6dckhu/cancel", + "web": "https://replicate.com/p/gm3qorzdhgbfurvjtvhg6dckhu" + } +} +``` + +### Prediction statuses + +- `starting` — booting up (may take seconds for warm models, minutes for cold) +- `processing` — model is running +- `succeeded` — done, `output` is populated +- `failed` — error occurred, `error` is populated +- `canceled` — canceled by the user + +## Sync mode with `Prefer: wait` + +Set the `Prefer` header to hold the connection open until the prediction finishes: + +``` +Prefer: wait=60 +``` + +The value is seconds (1–60). If the model doesn't finish in time, the response returns the prediction in its current state. Poll `urls.get` to get the final result. + +The Python SDK uses sync mode by default with `replicate.run()` (60-second timeout). Pass `wait=False` to disable it. + +## Webhooks + +Set `webhook` to an HTTPS URL when creating a prediction. Replicate POSTs the full prediction object when it reaches a terminal state. + +Filter events with `webhook_events_filter`: `["start", "output", "logs", "completed"]`. + +Validate webhook signatures using the `Webhook-ID`, `Webhook-Timestamp`, and `Webhook-Signature` headers. Get your webhook secret from `GET /v1/webhooks/default/secret`. + +## Prediction lifetime (auto-cancel) + +Set `lifetime` to auto-cancel predictions that run too long: + +```json +{"lifetime": "5m"} +``` + +Accepts durations like `30s`, `5m`, `1h`, `1h30m45s`. Measured from creation time. + +## Streaming (SSE) + +Models that support streaming include a `stream` URL in the response. Connect to it as an SSE `EventSource` to receive incremental output. Language models typically support streaming. + +## Pagination + +List endpoints return paginated responses with `next`, `previous`, and `results` fields. Follow the `next` URL to get the next page. The `results` array contains up to 100 items. + +## Error responses + +Errors return JSON with `title`, `detail`, and `status`: + +```json +{ + "title": "Not Found", + "detail": "Model not found: foo/bar", + "status": 404 +} +``` + +## Content negotiation + +Set `Accept: text/markdown` when fetching Replicate docs pages (e.g. `https://replicate.com/docs`) to get Markdown instead of HTML. + +## Output file URLs + +File outputs are served from `replicate.delivery` and its subdomains. Add `*.replicate.delivery` to your domain allow list. + +Output file URLs expire after 1 hour by default. Save a copy of any files you need to keep. diff --git a/skills/replicate/references/MODEL_SEARCH.md b/skills/replicate/references/MODEL_SEARCH.md new file mode 100644 index 0000000..48292f9 --- /dev/null +++ b/skills/replicate/references/MODEL_SEARCH.md @@ -0,0 +1,137 @@ +# Finding and Choosing Models + +## Search API + +The search endpoint finds models, collections, and docs by text query: + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + "https://api.replicate.com/v1/search?query=text+to+speech" | jq '[.models[:3][] | {name: .model.name, owner: .model.owner, runs: .model.run_count}]' +``` + +```python +import replicate + +page = replicate.models.search("text to speech") +for model in page.results[:3]: + print(f"{model.owner}/{model.name} — {model.run_count} runs") + print(f" {model.description}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const page = await replicate.models.search("text to speech"); +for (const model of page.results.slice(0, 3)) { + console.log(`${model.owner}/${model.name} — ${model.run_count} runs`); + console.log(` ${model.description}`); +} +``` + +Search returns metadata for each model result including `tags`, `generated_description`, and `run_count`. + +The search also returns matching collections. Use the [collections API](COLLECTIONS.md) to browse curated model groups. + +## Per-model docs + +Every model has an LLM-readable docs page: + +``` +https://replicate.com/{owner}/{model}/llms.txt +``` + +For example: + +## Reading model schemas + +Every model exposes its input/output schema via the models API: + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/models/black-forest-labs/flux-2-klein-9b \ + | jq '.latest_version.openapi_schema.components.schemas.Input.properties | to_entries[] | {name: .key, type: .value.type, description: .value.description}' +``` + +```python +import replicate + +model = replicate.models.get("black-forest-labs", "flux-schnell") +input_schema = model.latest_version.openapi_schema["components"]["schemas"]["Input"] +for name, prop in input_schema["properties"].items(): + required = name in input_schema.get("required", []) + print(f"{'*' if required else ' '} {name}: {prop.get('type', '?')} — {prop.get('description', '')}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const model = await replicate.models.get("black-forest-labs", "flux-schnell"); +const inputSchema = + model.latest_version.openapi_schema.components.schemas.Input; +const required = inputSchema.required || []; +for (const [name, prop] of Object.entries(inputSchema.properties)) { + console.log( + `${required.includes(name) ? "*" : " "} ${name}: ${prop.type || "?"} — ${prop.description || ""}`, + ); +} +``` + +The schema path is: `model.latest_version.openapi_schema.components.schemas.Input.properties` + +Each property may include: +- `type` — data type (`string`, `integer`, `number`, `boolean`) +- `description` — what the input does +- `default` — default value +- `minimum` / `maximum` — numeric bounds +- `enum` — allowed values +- `format` — e.g. `uri` for file inputs +- `x-order` — display order on the model page + +## Validating inputs + +After fetching a schema, validate your inputs: + +- Check `required` fields are present +- Check numeric values are within `minimum`/`maximum` bounds +- Check string values are in `enum` if specified +- Don't set optional inputs unless you have a reason — let defaults work + +If unsure about a parameter value, check the model's `default_example` (returned by the models.get endpoint) to see what inputs were used in a working prediction. + +## Picking the right model + +- **Prefer official models.** They're always warm (no cold boot), have stable APIs, and predictable pricing. They're in the `official` collection. +- **Prefer the latest version.** If search returns Kling 2.5 and Kling 3.0, use Kling 3. Use Nano Banana Pro instead of Nano Banana. Use FLUX.1 Pro over FLUX.1 Schnell for quality. +- **Run count can be misleading.** Old models accumulate runs over time but may be outdated. A model with 10M runs from 2023 is likely worse than a model with 100K runs from 2025. +- **Prefer recently released models.** The AI space moves fast. Most of the thousands of models on Replicate are from the past five years; many are outdated. +- **Check model tags.** Search results include tags like `image-generation`, `video`, `audio` to help filter. + +## Listing model versions + +Each model can have multiple versions. Official models route to the latest automatically, but community models require a specific version ID. Only community models expose a version list. + +```bash +curl -s -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + https://api.replicate.com/v1/models/stability-ai/sdxl/versions | jq '[.results[:3][] | {id: .id, created_at: .created_at}]' +``` + +```python +import replicate + +model = replicate.models.get("stability-ai", "sdxl") +versions = model.versions.list() +for v in versions.results[:3]: + print(f"{v.id} — created {v.created_at}") +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const versions = await replicate.models.versions.list("stability-ai", "sdxl"); +for (const v of versions.results.slice(0, 3)) { + console.log(`${v.id} — created ${v.created_at}`); +} +``` diff --git a/skills/replicate/references/PREDICTIONS.md b/skills/replicate/references/PREDICTIONS.md new file mode 100644 index 0000000..fe36751 --- /dev/null +++ b/skills/replicate/references/PREDICTIONS.md @@ -0,0 +1,316 @@ +# Running Predictions + +## Official vs community models + +**Official models** (`owner/name` format): +- Always warm — no cold-boot wait +- Stable API interfaces +- Predictable per-run pricing +- Maintained by Replicate staff + +**Community models** (`owner/name:version_id` format): +- Can cold-boot (seconds to minutes for a new worker to start) +- You must pin a specific version ID +- Maintained by the model author — interfaces may change +- Can be made always-warm with a [deployment](DEPLOYMENTS.md) + +The `POST /v1/predictions` endpoint handles both. Pass `version` as `owner/name` for official models or `owner/name:version_id` for community models. + +## Create a prediction (async) + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"version": "black-forest-labs/flux-2-klein-9b", "input": {"prompt": "a red panda in a bamboo forest", "num_outputs": 1}}' \ + https://api.replicate.com/v1/predictions | jq '{id, status}' +``` + +```python +import replicate + +prediction = replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, +) +print(prediction.id, prediction.status) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const prediction = await replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a red panda in a bamboo forest", num_outputs: 1 }, +}); +console.log(prediction.id, prediction.status); +``` + +## Poll for results + +```python +import replicate +import time + +prediction = replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, +) +while prediction.status not in ("succeeded", "failed", "canceled"): + time.sleep(1) + prediction = replicate.predictions.get(prediction.id) + +print(prediction.status) +if prediction.output: + for url in prediction.output: + print(url) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +let prediction = await replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a red panda in a bamboo forest", num_outputs: 1 }, +}); +while (!["succeeded", "failed", "canceled"].includes(prediction.status)) { + await new Promise((r) => setTimeout(r, 1000)); + prediction = await replicate.predictions.get(prediction.id); +} +console.log(prediction.status); +if (prediction.output) { + for (const url of prediction.output) { + console.log(url); + } +} +``` + +## Synchronous mode with `Prefer: wait` + +Hold the connection open for up to 60 seconds. If the model finishes in time, the response includes the completed prediction with `output` populated. + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Prefer: wait=60" \ + -d '{"version": "black-forest-labs/flux-2-klein-9b", "input": {"prompt": "a red panda in a bamboo forest", "num_outputs": 1}}' \ + https://api.replicate.com/v1/predictions | jq '{id, status, output}' +``` + +The Python SDK's `replicate.run()` uses sync mode by default with a 60-second timeout. + +If the model doesn't finish in time, the response returns the prediction in its current state. The `Location` response header and `urls.get` field both point to the polling URL. + +## `replicate.run()` — convenience wrapper + +`replicate.run()` creates a prediction, waits for it, and returns the output directly. It uses sync mode with a 60-second timeout. + +```python +import replicate + +output = replicate.run( + "black-forest-labs/flux-2-klein-9b", + input={"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, +) +for item in output: + print(item.url) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const output = await replicate.run("black-forest-labs/flux-2-klein-9b", { + input: { prompt: "a red panda in a bamboo forest", num_outputs: 1 }, +}); +console.log(output); +``` + +## File inputs + +Prefer HTTPS URLs for file inputs. Base64 is supported but slower and increases request size. + +```python +import replicate + +output = replicate.run( + "black-forest-labs/flux-kontext-pro", + input={ + "prompt": "make it look like a watercolor painting", + "input_image": "https://picsum.photos/id/237/200/300.jpg", + }, +) +print(output.url) +``` + +Output file URLs expire after 1 hour. Download and store them immediately if you need to keep them. + +## Concurrent predictions + +Fire off multiple predictions in parallel. Don't wait for one to finish before starting the next. + +```python +import replicate +import time + +prompts = [ + "a red panda eating a bamboo sandwich", + "a blue parrot riding a bicycle", + "a green iguana playing chess", +] + +predictions = [ + replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": p, "num_outputs": 1}, + ) + for p in prompts +] + +results = {} +while len(results) < len(predictions): + time.sleep(1) + for pred in predictions: + if pred.id not in results: + p = replicate.predictions.get(pred.id) + if p.status in ("succeeded", "failed", "canceled"): + results[pred.id] = p + +for pred in results.values(): + print(pred.status, pred.output) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const prompts = [ + "a red panda eating a bamboo sandwich", + "a blue parrot riding a bicycle", + "a green iguana playing chess", +]; + +const predictions = await Promise.all( + prompts.map((prompt) => + replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt, num_outputs: 1 }, + }), + ), +); + +const poll = async (id) => { + let pred = await replicate.predictions.get(id); + while (!["succeeded", "failed", "canceled"].includes(pred.status)) { + await new Promise((r) => setTimeout(r, 1000)); + pred = await replicate.predictions.get(id); + } + return pred; +}; + +const results = await Promise.all(predictions.map((p) => poll(p.id))); +for (const result of results) { + console.log(result.status, result.output); +} +``` + +## Webhooks + +Set `webhook` on the prediction to receive a POST when it completes. Use `webhook_events_filter` to receive only certain events. + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "version": "black-forest-labs/flux-2-klein-9b", + "input": {"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, + "webhook": "https://example.com/webhook", + "webhook_events_filter": ["completed"] + }' \ + https://api.replicate.com/v1/predictions | jq '{id, status}' +``` + +Webhook events: `start`, `output`, `logs`, `completed`. + +Replicate signs webhook requests. Validate using the `Webhook-ID`, `Webhook-Timestamp`, and `Webhook-Signature` headers. Get the signing secret from `GET /v1/webhooks/default/secret`. + +## Cancel a prediction + +```python +import replicate + +prediction = replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, +) +cancelled = replicate.predictions.cancel(prediction.id) +print(cancelled.status) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const prediction = await replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a red panda in a bamboo forest", num_outputs: 1 }, +}); +const cancelled = await replicate.predictions.cancel(prediction.id); +console.log(cancelled.status); +``` + +## Prediction lifetime (auto-cancel) + +Set `lifetime` to cancel predictions that run too long: + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "version": "black-forest-labs/flux-2-klein-9b", + "input": {"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, + "lifetime": "5m" + }' \ + https://api.replicate.com/v1/predictions | jq '{id, status}' +``` + +Accepts `30s`, `5m`, `1h`, `1h30m45s`. Measured from the creation time. + +## Streaming (SSE) + +Models that support streaming (typically language models) include a `stream` URL in the response. Use SSE to receive incremental output. + +```python +import replicate + +prediction = replicate.predictions.create( + model="meta/meta-llama-3-8b-instruct", + input={"prompt": "write a haiku about mountains"}, + stream=True, +) +for event in prediction.stream(): + print(str(event), end="", flush=True) +print() +``` + +## Async Python + +```python +import asyncio +import replicate + +async def main(): + output = await replicate.async_run( + "black-forest-labs/flux-2-klein-9b", + input={"prompt": "a red panda in a bamboo forest", "num_outputs": 1}, + ) + for item in output: + print(item.url) + +asyncio.run(main()) +``` diff --git a/skills/replicate/references/WORKFLOWS.md b/skills/replicate/references/WORKFLOWS.md new file mode 100644 index 0000000..00b48e3 --- /dev/null +++ b/skills/replicate/references/WORKFLOWS.md @@ -0,0 +1,123 @@ +# Multi-Model Workflows + +Complex tasks often require chaining multiple models together. The core pattern is always the same: run models in parallel where possible, pass outputs as inputs to the next step, and stitch results together. + +## Core pattern: parallel predictions + +Don't wait for one prediction to finish before starting the next. Start all predictions you can, then collect results. + +```python +import replicate +import time + +pred_a = replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": "a sunrise over mountains"}, +) +pred_b = replicate.predictions.create( + model="black-forest-labs/flux-2-klein-9b", + input={"prompt": "a sunset over mountains"}, +) + +def wait(pred_id): + while True: + p = replicate.predictions.get(pred_id) + if p.status in ("succeeded", "failed", "canceled"): + return p + time.sleep(1) + +result_a, result_b = wait(pred_a.id), wait(pred_b.id) +print(result_a.output) +print(result_b.output) +``` + +```javascript +const Replicate = require("replicate"); +const replicate = new Replicate(); + +const [predA, predB] = await Promise.all([ + replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a sunrise over mountains" }, + }), + replicate.predictions.create({ + model: "black-forest-labs/flux-2-klein-9b", + input: { prompt: "a sunset over mountains" }, + }), +]); + +const poll = async (id) => { + let pred = await replicate.predictions.get(id); + while (!["succeeded", "failed", "canceled"].includes(pred.status)) { + await new Promise((r) => setTimeout(r, 1000)); + pred = await replicate.predictions.get(id); + } + return pred; +}; + +const [resultA, resultB] = await Promise.all([poll(predA.id), poll(predB.id)]); +console.log(resultA.output); +console.log(resultB.output); +``` + +## Pass outputs as inputs + +Model output URLs can be passed directly as file inputs to the next model. They're valid for 1 hour. + +```python +import replicate + +image_output = replicate.run( + "black-forest-labs/flux-2-klein-9b", + input={"prompt": "a serene mountain lake at dawn"}, +) +image_url = image_output[0].url + +caption_output = replicate.run( + "andreasjansson/blip-2:f677695e5e89f8b236e52ecd1d3f01beb44c34606419bcc19345e046d8f786f9", + input={"image": image_url, "question": "What is in this image?"}, +) +print(caption_output) +``` + +## Pattern: video generation pipeline + +To generate a video from a description: +1. Generate keyframe images with an image model (run in parallel) +2. Generate video clips between keyframes with a video model (run in parallel) +3. Stitch clips together + +The exact models depend on the task. Search for the latest video generation models before starting. + +## Pattern: image editing pipeline + +Edit an image in multiple steps — remove background, apply style, upscale: + +```python +import replicate + +source_image = "https://picsum.photos/id/237/200/300.jpg" + +styled_output = replicate.run( + "black-forest-labs/flux-kontext-pro", + input={ + "prompt": "make it look like a watercolor painting", + "input_image": source_image, + }, +) +print("Styled image:", styled_output.url) +``` + +## Pattern: audio/video translation + +Translate a video's speech to another language: +1. Transcribe speech to text with a speech recognition model +2. Translate the text (using your own LLM capabilities — no model call needed) +3. Generate speech in the target language +4. Lip-sync the new audio to the original video + +## Pattern: document extraction + +For PDFs or documents, use OCR to extract text, then use your own language model to process it. There's no need to run an LLM on Replicate for text processing — you're already running in one. + + diff --git a/test/fixtures/me.png b/test/fixtures/me.png new file mode 100644 index 0000000..0528df5 Binary files /dev/null and b/test/fixtures/me.png differ diff --git a/test/package-lock.json b/test/package-lock.json new file mode 100644 index 0000000..47b4593 --- /dev/null +++ b/test/package-lock.json @@ -0,0 +1,1589 @@ +{ + "name": "test", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test", + "dependencies": { + "replicate": "^1.4.0", + "wrangler": "^4.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.14.0.tgz", + "integrity": "sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==", + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "^1.20260218.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260302.0.tgz", + "integrity": "sha512-cGtxPByeVrgoqxbmd8qs631wuGwf8yTm/FY44dEW4HdoXrb5jhlE4oWYHFafedkQCvGjY1Vbs3puAiKnuMxTXQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260302.0.tgz", + "integrity": "sha512-WRGqV6RNXM3xoQblJJw1EHKwx9exyhB18cdnToSCUFPObFhk3fzMLoQh7S+nUHUpto6aUrXPVj6R/4G3UPjCxw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260302.0.tgz", + "integrity": "sha512-gG423mtUjrmlQT+W2+KisLc6qcGcBLR+QcK5x1gje3bu/dF3oNiYuqY7o58A+sQk6IB849UC4UyNclo1RhP2xw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260302.0.tgz", + "integrity": "sha512-7M25noGI4WlSBOhrIaY8xZrnn87OQKtJg9YWAO2EFqGjF1Su5QXGaLlQVF4fAKbqTywbHnI8BAuIsIlUSNkhCg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260302.0.tgz", + "integrity": "sha512-jK1L3ADkiWxFzlqZTq2iHW1Bd2Nzu1fmMWCGZw4sMZ2W1B2WCm2wHwO2SX/py4BgylyEN3wuF+5zagbkNKht9A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz", + "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", + "license": "CC0-1.0" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/miniflare": { + "version": "4.20260302.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260302.0.tgz", + "integrity": "sha512-joGFywlo7HdfHXXGOkc6tDCVkwjEncM0mwEsMOLWcl+vDVJPj9HRV7JtEa0+lCpNOLdYw7mZNHYe12xz9KtJOw==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.18.2", + "workerd": "1.20260302.0", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/replicate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/replicate/-/replicate-1.4.0.tgz", + "integrity": "sha512-1ufKejfUVz/azy+5TnzQP7U1+MHVWZ6psnQ06az8byUUnRhT+DZ/MvewzB1NQYBVMgNKR7xPDtTwlcP5nv/5+w==", + "license": "Apache-2.0", + "engines": { + "git": ">=2.11.0", + "node": ">=18.0.0", + "npm": ">=7.19.0", + "yarn": ">=1.7.0" + }, + "optionalDependencies": { + "readable-stream": ">=4.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20260302.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260302.0.tgz", + "integrity": "sha512-FhNdC8cenMDllI6bTktFgxP5Bn5ZEnGtofgKipY6pW9jtq708D1DeGI6vGad78KQLBGaDwFy1eThjCoLYgFfog==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260302.0", + "@cloudflare/workerd-darwin-arm64": "1.20260302.0", + "@cloudflare/workerd-linux-64": "1.20260302.0", + "@cloudflare/workerd-linux-arm64": "1.20260302.0", + "@cloudflare/workerd-windows-64": "1.20260302.0" + } + }, + "node_modules/wrangler": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.68.1.tgz", + "integrity": "sha512-G+TI3k/olEGBAVkPtUlhAX/DIbL/190fv3aK+r+45/wPclNEymjxCc35T8QGTDhc2fEMXiw51L5bH9aNsBg+yQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.14.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.3", + "miniflare": "4.20260302.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260302.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260302.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + } + } +} diff --git a/test/package.json b/test/package.json new file mode 100644 index 0000000..2f12686 --- /dev/null +++ b/test/package.json @@ -0,0 +1,9 @@ +{ + "name": "test", + "private": true, + "type": "commonjs", + "dependencies": { + "replicate": "^1.4.0", + "wrangler": "^4.0.0" + } +}