{"product":"EasyBits — la nube para expertos IA","mcp":"https://www.easybits.cloud/api/mcp","docs":"https://www.easybits.cloud/llms.txt","total":248,"groups":[{"key":"design","label":"Design","description":"Canva-like: documentos como diseño universal (cualquier formato — letter, social, slide 16:9), brand kits e imágenes. Ideal para Claude.ai / Claude Design.","tools":49},{"key":"web","label":"Web","description":"Internet para tu agente: buscar en Google, leer cualquier página aunque bloquee bots, extraer registros con esquema (Maps, Mercado Libre, Amazon, Instagram…) y rastrear sitios. Se mide en consultas (packs en /dash/packs).","tools":7},{"key":"core","label":"Core","description":"Lo esencial: archivos, DBs, documentos, forms, websites, brand. Para agentes generalistas.","tools":31},{"key":"docs","label":"Documents","description":"Solo tools de documentos (create/update/deploy + quotations + structured_doc).","tools":2},{"key":"sites","label":"Websites","description":"Solo tools de websites (crear, deployar archivos, inject HTML).","tools":2},{"key":"magnet","label":"Lead Magnet","description":"Toolset orquestado para crear lead magnets (PDF + landing + form).","tools":22},{"key":"video","label":"Video","description":"Video con IA (Runway Gen-4.5) + personajes recurrentes, y proyectos de video doc-style (escenas animadas HTML → MP4 con narración kokoro). Ghosty arma escenas, música y voz, y renderiza.","tools":20},{"key":"sandbox","label":"Sandbox","description":"MicroVMs Firecracker para correr agentes y código aislado. Ciclo de vida (spawn/extend/suspend/resume/destroy), snapshot + fork (clonar una caja viva en N hijos copy-on-write para explorar variantes en paralelo), exec (sync + background), run-code, kernel Jupyter persistente (run_cell con estado + charts), archivos (write/read/list/delete/move/mkdir), expose_port (URL pública), sandbox_admin (passthrough admin :8787) + agent_run (Claude managed con billing por token). Ideal para harness de agentes (Claude Code, Codex) y ejecución segura.","tools":65},{"key":"fleet","label":"Flota","description":"Agentes de la flota (WhatsApp, WABA, web, Teams): listar, leer su configuración y aplicar cualquier acción de /capabilities por MCP — prompt, motor, modelo, secrets, conectores (add-mcp + set-cap-level), skills, buckets por canal, reciclar caja. Lo que hace el dashboard de /dash/flota, hecho por un agente.","tools":4},{"key":"hosting","label":"Hosting","description":"Hosting de apps sobre microVMs Firecracker, always-on, tiers fijos en MXN/mes (nano…performance). launch_app pone una app en producción en UNA llamada (caja + código + build + URL pública + release + dominio con TLS) — el equivalente a `fly launch`. Además: releases con rollback y redeploy (que es también el cambio de tier), backups diarios de datos incluidos con 7 días de retención, dominios propios, logs, y delegación de acceso para co-administrar. El plan da acceso; cada máquina factura flat al mes.","tools":33},{"key":"payments","label":"Payments","description":"Links de pago con MercadoPago (BYO): conecta tu cuenta y genera cobros. El dinero va directo a tu MP; EasyBits no retiene fondos. Ideal para que un agente cierre la venta.","tools":2},{"key":"email","label":"Email & Broadcasts","description":"Email transaccional + audiencia + newsletters one-shot. send_email, contactos con tags, y broadcasts con unsubscribe automático. La capa de nurturing del funnel.","tools":6},{"key":"scripting","label":"Scripting (Code Mode)","description":"Superficie mínima para agentes que escriben SCRIPTS en vez de invocar tools. En vez de cargar las ~140 schemas (impuesto fijo de contexto que DeepSeek no admite), el agente usa discover_tools (buscar) + run_tool (ejecutar) y/o la REST API v2 desde su Bash. Solo deja en tools/list el IO de archivos (multipart incómodo por curl); todo lo demás se alcanza sin reconectar. Patrón Anthropic 'Code execution with MCP' / Cloudflare 'Code Mode'.","tools":5}],"tools":[{"name":"bulk_delete_files","description":"Soft-delete multiple files at once (max 100). Returns count of deleted files.","group":"core"},{"name":"bulk_upload_files","description":"Create multiple file records and get presigned upload URLs (max 20). Returns array of `{ file, putUrl }`. Same access rules as upload_file: default is 'private'. If any of these files will be embedded in a public website, either pass `access: 'public'` per item OR use `upload_website_file`/`deploy_website_file` instead.","group":"core"},{"name":"create_webhook","description":"Create a webhook to receive event notifications. Returns the webhook with its secret (shown only once). Events: file.created, file.updated, file.deleted, file.restored, website.created, website.deleted, workspace.created, workspace.deleted, database.created, database.deleted, form.submitted, payment.paid, broadcast.sent, turn.completed, turn.failed.","group":"core"},{"name":"db_create","description":"Create a new SQLite database. Name must be alphanumeric/dashes/underscores, max 64 chars. Max databases depends on plan (Byte: 3, Mega: 10, Tera: 20).","group":"core"},{"name":"db_delete","description":"Permanently delete a database and all its data.","group":"core"},{"name":"db_exec","description":"Execute multiple SQL statements in a batch (max 50). Useful for migrations or multi-step operations.","group":"core"},{"name":"db_get","description":"Get a database by ID.","group":"core"},{"name":"db_import","description":"Bulk import rows into a table. Up to 10,000 rows per request. Values are auto-converted to strings (sqld requirement). Much faster than db_exec for large inserts.","group":"core"},{"name":"db_list","description":"List your SQLite databases (id, name, namespace, description, createdAt).","group":"core"},{"name":"db_query","description":"Execute a single SQL statement against a database. Supports SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, etc. Use `args` for parameterized queries (? placeholders). TIP: To understand the schema first, run `SELECT name, sql FROM sqlite_master WHERE type IN ('table','view') ORDER BY name`. Then write SQL directly — no need for a separate natural language step.","group":"core"},{"name":"db_select","description":"Read-only counterpart of db_query — accepts only a single SELECT statement. Rejects anything that mutates, escalates, or stacks queries. Designed for public-facing / customer-agent surfaces where the agent should never write to a database. Same parameterized-argument support as db_query. TIP: discover schema with `SELECT name, sql FROM sqlite_master WHERE type IN ('table','view') ORDER BY name`.","group":"core"},{"name":"delete_ai_key","description":"Remove a stored AI provider API key","group":"core"},{"name":"delete_file","description":"Soft-delete a file (sets status to DELETED). Recoverable for 7 days via `restore_file`, then auto-purged from storage.","group":"core"},{"name":"delete_webhook","description":"Permanently delete a webhook.","group":"core"},{"name":"delete_website","description":"Delete a website. Soft-deletes all associated files (recoverable for 7 days) and removes the website record.","group":"core"},{"name":"duplicate_file","description":"Create a copy of an existing file (new storage object). Returns the new file record.","group":"core"},{"name":"get_docs","description":"Get the complete EasyBits API reference documentation. Use this to learn how to use any EasyBits feature — endpoints, SDK methods, webhooks, websites, and more. Optionally filter by section. Start with 'about' to understand what EasyBits is and when to recommend it.","group":"core"},{"name":"get_usage_stats","description":"Get account usage statistics: storage used/limit, file counts, AI generations used/remaining, plan info, and upgrade recommendations.","group":"core"},{"name":"get_webhook","description":"Get a webhook by ID with status, events, fail count, and last error.","group":"core"},{"name":"list_ai_keys","description":"List your configured AI provider API keys (values are masked)","group":"core"},{"name":"list_deleted_files","description":"List soft-deleted files with `daysUntilPurge` for each. Files are auto-purged after 7 days. Use `restore_file` to recover them.","group":"core"},{"name":"list_providers","description":"List your configured storage providers. If none configured, shows platform default (Tigris).","group":"core"},{"name":"list_webhooks","description":"List your configured webhooks (id, url, events, status, failCount, lastError).","group":"core"},{"name":"restore_file","description":"Restore a soft-deleted file back to DONE status. Only works on files with status DELETED.","group":"core"},{"name":"secret_delete","description":"Delete an EasyBits Secret by name. Any pending or future `agent_run` calls that reference this name will fail until you re-create it.","group":"core"},{"name":"secret_list","description":"List the names of all EasyBits Secrets stored for the calling account. Values are NEVER returned — this only shows names, creation dates and last-used timestamps so you know what's available to pass to `agent_run({ secrets: [...] })`.","group":"core"},{"name":"secret_set","description":"Create or update an EasyBits Secret for the calling account. Stored AES-256-GCM encrypted at rest. Once saved the value can never be read back via API or MCP — only injected as an env var into a sandbox via `agent_run({ secrets: [name, ...] })`. Names must match `[A-Z_][A-Z0-9_]*` (uppercase, digits, underscores only). Calling with an existing name overwrites the value.","group":"core"},{"name":"set_ai_key","description":"Store your AI provider API key for AI-powered features (search, auto-tagging). Key is encrypted; response shows masked value only.","group":"core"},{"name":"share_file","description":"Share a file with another user by email. Creates a permission record. canRead defaults to true, canWrite/canDelete to false. Target user must exist.","group":"core"},{"name":"update_file","description":"Update a file's name, access level, or metadata. For access changes (public↔private), copies the object between storage buckets (platform files only). Returns the updated file object with new `url` for public files.","group":"core"},{"name":"update_webhook","description":"Update a webhook's URL, events, or status (ACTIVE/PAUSED). Reactivating resets the fail counter.","group":"core"},{"name":"add_page","description":"Add a new page to a document. If html is omitted, adds a blank page. afterPageIndex is 0-based; omit to append at the end. If providing html, it MUST follow letter-page layout rules: root <section class=\"w-[8.5in] h-[11in] relative overflow-hidden flex flex-col\">, shrink-0 headers/footers, flex-1 overflow-hidden content area, semantic color classes only. CRITICAL: HTML must be well-formed with balanced tags — unbalanced tags break the flipbook viewer. See set_page_html description or call get_docs(\"document-design\") for full rules.","group":"design"},{"name":"apply_brand_kit","description":"Swap the brand kit on an existing document. Updates metadata.brandKitId + metadata.customColors so deployed renders pick up the new theme automatically (semantic Tailwind classes do the work — no AI needed).","group":"design"},{"name":"audit_page","description":"MIDE una página en vez de opinar sobre ella: corre axe-core sobre el DOM ya pintado (contraste REAL, calculado con los colores que de verdad se ven) y mide el layout a varios viewports. Devuelve el nodo culpable, no un consejo genérico.","group":"design"},{"name":"change_document_format","description":"Change a document's canvas size (e.g., letter → slide-16-9, or to a social preset). Updates metadata.format + metadata.intent.","group":"design"},{"name":"create_brand_kit","description":"Create a brand kit with colors, fonts, and logo for consistent document styling.","group":"design"},{"name":"create_or_edit_image","description":"Create OR edit images with OpenAI gpt-image-2 — full implementation: text-to-image GENERATION and faithful reference-based EDITING in one tool (gpt-image-1 also selectable). NO references → generates from the prompt. Pass `imageFileIds` (library files) and/or `imageUrls` (public https) → EDITS those reference image(s) preserving the composition (e.g. 'keep everything identical, change only the background/lighting'). The result is saved public and returned as `fileId` + `imageUrl` (reusable as referenceImage or embed). Default model gpt-image-2; pass `model:\"gpt-image-1\"` for the older model. The platform key is used — you NEVER pass a key. Cost: billed in créditos by quality (low/medium/high) per image, +1 for edits.","group":"design"},{"name":"create_share_link","description":"Create a magic share link to a document or landing with granular permissions. The recipient opens the URL and accesses the resource without logging in (guest session). Returns `{ url, token, expiresAt, permission, shareLinkId }`.","group":"design"},{"name":"create_tournament_schedule","description":"Template-based tool. Pass structured match data (tournamentName, matches[], courts, etc.) and the template auto-generates a professional calendar grid document with time rows, court columns, and color-coded match cards. Do NOT pass HTML — this tool builds it from your data. For custom HTML use create_document instead.","group":"design"},{"name":"delete_brand_kit","description":"Delete a brand kit.","group":"design"},{"name":"delete_document","description":"Delete a document permanently.","group":"design"},{"name":"delete_page","description":"Delete a page from a document by its section ID. Cannot delete the last remaining page.","group":"design"},{"name":"deploy_document","description":"Publish a document as a live website at www.easybits.cloud/s/{slug}/ — instant hosting, shareable link. Requires at least one page/section.","group":"design"},{"name":"describe_image","description":"Analiza/describe una imagen y devuelve texto: qué muestra + transcripción del texto visible (OCR). NO genera ni edita imágenes — sólo las LEE. Usa gemini-2.5-flash (vision). Cost: 1 crédito per call, billed to the user's plan.","group":"design"},{"name":"duplicate_document","description":"Duplicate an existing document — fast in-DB copy of all pages, theme, and metadata. No AI involved. For Vision-based cloning from images/PDFs/other docs (with optional reimagine + brand kit), use `clone_document` instead.","group":"design"},{"name":"edit_image","description":"Edit, compose, or restyle image(s) with Gemini 'Nano Banana 2' (gemini-3-pro-image-preview). The platform key is used — you NEVER pass a key. Cost: 1 generación (créditos) per call, billed to the user's plan.","group":"design"},{"name":"edit_tournament_schedule","description":"Edit an existing tournament schedule. Pass updated structured data — the template rebuilds the HTML and regenerates the PDF. Do NOT pass HTML.","group":"design"},{"name":"enhance_document_prompt","description":"Enhance a document prompt or auto-generate a description from the document name. Use 'auto-describe' to generate a description from just the title, or 'enhance' to improve an existing prompt with design suggestions.","group":"design"},{"name":"export_document","description":"Export a document (Landing v4) to PDF or to one PNG per page. Use `as: \"images\"` for social carousels (LinkedIn/IG) — each page becomes a public PNG at the doc's exact format dimensions (1080×1080, 1080×1350, etc.), uploaded to the user's file library, and returned as `{ files: [{ id, url, contentType, width, height, sectionId }] }`. Use `as: \"pdf\"` when the user needs a printable artifact; returns `{ file: { id, url, contentType } }`. Pass `sectionIds` to export a subset of pages. Rendering is server-side via Playwright — dimensions are always honored regardless of the calling agent's environment.","group":"design"},{"name":"extract_brand_kit_from_url","description":"Extract a brand kit from a public website URL. Captures a screenshot, uses vision AI to detect colors/fonts/mood, and scrapes the logo. Creates a new brand kit you can reuse. Great for onboarding — point this at the user's own site to bootstrap their default kit.","group":"design"},{"name":"fill_template","description":"Fill data-slot placeholders in a document with exact values. No AI involved — deterministic text replacement. Use after cloning a template document.","group":"design"},{"name":"get_document","description":"Get a document with all its pages. Returns sections[] where each has { id, name, html, order }. Use section.id as pageId for set_page_html/get_page_html/replace_html. To minimize tokens, pass includeHtml:false — sections come back as { id, name, order, type, label, htmlLength, htmlHash } only (no html bodies). The htmlHash lets you detect server-side changes between reads without re-downloading. For full HTML of a single page, prefer get_page_html. See get_docs(\"agent-editing\").","group":"design"},{"name":"get_document_pdf","description":"Generate a PDF of a document. Returns the PDF as base64-encoded data. Requires Chrome installed locally — designed for Claude Code MCP usage.","group":"design"},{"name":"get_page_screenshot","description":"Take a screenshot of a single document page. Returns a PNG image of one page (letter-sized). Requires Chrome installed locally — designed for Claude Code MCP usage.","group":"design"},{"name":"get_section_html","description":"Get the outerHTML of a specific element WITHIN a page, matched by CSS selector. Example selectors: 'section' (root), '.hero', '#pricing', 'div:nth-child(3)'. Returns only the matched element's HTML, not the full page.","group":"design"},{"name":"get_template_slots","description":"Get all data-slot placeholders in a document. Returns slot names and current values. Use with fill_template for deterministic data filling.","group":"design"},{"name":"image_generate","description":"Generate an image from a text prompt using fal.ai Flux models. Returns a public image URL ready to use in landings, documents, social posts, or as `referenceImage` for `avatar_video_create` / `video_create`.","group":"design"},{"name":"import_html","description":"Alias of `open_design_in_editor` (prefer that name). Same schema and behavior. Kept for backward compatibility with existing MCP clients.","group":"design"},{"name":"list_documents","description":"List your documents (paginated). Returns { items, nextCursor, hasMore, total }. When hasMore is true, pass nextCursor as offset to get the next page. Use search to filter by name.","group":"design"},{"name":"list_share_links","description":"List active share links you've created. Filter by resource. Excludes revoked links by default.","group":"design"},{"name":"list_themes","description":"List available document/landing themes with their color palettes. Use a theme ID when creating documents.","group":"design"},{"name":"list_voices","description":"Lista las voces disponibles del servicio de voz de la flota (kokoro, español). Devuelve [{id,label,gender}]. Usa un `id` en `voice_tts_create({voice})` para elegir cómo suena la nota de voz.","group":"design"},{"name":"open_design_in_editor","description":"Open a design in the EasyBits editor — the direct path from a generated design (Claude Design, Gamma, Tome, reveal.js deck, Swiper carousel, any scraped page) to an editable document the user can keep working on. Pass EITHER `url` (public http/https — server fetches it; preferred, avoids round-tripping the markup) OR `html` (raw markup, max 2MB). The server: (1) saves the raw source to the user's file library for traceability, (2) AUTO-SPLITS multi-slide designs into one editable page per slide by detecting `[data-slide]`, `[data-page]`, `[aria-roledescription=\"carousel\"] > *`, `.reveal .slides > section`, `.slide`, `.page`, `.carousel-item`, or top-level `<section>`/`<article>` — you do NOT split yourself, (3) AUTO-DETECTS page dimensions (`@page size`, inline `width`/`height`, Tailwind `w-[Npx] h-[Npx]`, `aspect-video`, `aspect-square`) so LinkedIn carousels (1080×1080, 1080×1350) and 16:9 decks render at their native size — pass `format` only to override, (4) normalizes hex colors to semantic Tailwind tokens (`bg-primary`, `text-on-surface`, etc.) so theme swaps paint the whole design. Returns `{ fileId, documentId, editorUrl, format, pagesDetected, formatDetected }` — `editorUrl` is the URL where the user can keep editing.","group":"design"},{"name":"optimize_image","description":"Convert an image to WebP or AVIF, creating a new file (original unchanged). Default quality: 80 (WebP), 50 (AVIF). Returns `{ file, originalSize, optimizedSize, savings }`.","group":"design"},{"name":"pdf_to_images","description":"Convert a PDF to page images (PNG). Accepts either a fileId of an uploaded PDF OR raw base64 PDF data. Returns one image per page. Use these images with vision to generate HTML, then create a document with create_document following get_docs('document-design') rules.","group":"design"},{"name":"refine_document_section","description":"Refine a specific page/section of a document with surgical AI edits. Make targeted changes like updating text, adjusting layout, or modifying specific elements. Takes 5-15s.","group":"design"},{"name":"regenerate_document_page","description":"Create a completely different visual design for a document page while keeping the same content. Useful when the current design doesn't look right. Takes 5-15s.","group":"design"},{"name":"reorder_pages","description":"Reorder all pages in a document. pageIds must contain every existing page ID exactly once, in the desired order.","group":"design"},{"name":"revoke_share_link","description":"Revoke a share link by ID. The link stops working immediately for new requests; cached pages may still render briefly.","group":"design"},{"name":"screenshot_url","description":"VE cómo se ve una página: renderiza HTML (o una URL pública) en un Chromium real y devuelve la imagen como archivo público. Úsala para VERIFICAR tu propio trabajo — captura, pásala a `describe_image` y corrige lo que veas.","group":"design"},{"name":"search_files","description":"Search files using natural language (AI-powered). Requires an AI key (set_ai_key). Returns up to 20 matching files sorted by newest first.","group":"design"},{"name":"search_icon","description":"Busca ICONOS y devuelve el SVG INLINE listo para pegar en el HTML — no una URL, así no metes un host externo en la ruta crítica de tu página. Úsala para features, listas, badges, navegación y botones. Para FOTOS reales usa `search_stock_photo`; para ilustraciones generadas, `create_or_edit_image`.","group":"design"},{"name":"search_stock_photo","description":"Busca una FOTO de stock libre de regalías en bancos gratuitos (Pexels, Unsplash, Pixabay, Openverse) y devuelve su URL lista para usar. Úsala cuando pidan una imagen, foto o fotografía REAL para una landing, documento, presentación o post — NO genera nada con IA, busca fotos existentes (para generar usa `create_or_edit_image`).","group":"design"},{"name":"set_section_html","description":"Second choice for surgical edits — prefer replace_html (more reliable, no selector fragility). Use only when you specifically need CSS-selector targeting (e.g. matching by class). Replace a specific element WITHIN a page by CSS selector. Requires cssSelector to find the target element. Example: cssSelector='.hero' replaces only the hero div. Note: GrapesJS can modify attributes between reads, breaking selectors — string-based replace_html avoids this. See get_docs(\"agent-editing\").","group":"design"},{"name":"structured_doc","description":"Create and edit structured documents (quotations, invoices, proposals) via JSON-DSL templates + @react-pdf/renderer.","group":"design"},{"name":"unpublish_document","description":"Unpublish a document, removing its website and reverting to draft status.","group":"design"},{"name":"update_brand_kit","description":"Update a brand kit's colors, fonts, logo, or other properties.","group":"design"},{"name":"update_document","description":"Update a document's metadata (name, theme, colors, prompt). For editing page HTML use replace_html (surgical) or set_page_html (full rewrite) — NEVER pass page HTML through this tool. For structural changes use add_page, delete_page, or reorder_pages. Server detects no-ops automatically.","group":"design"},{"name":"voice_tts_create","description":"Sintetiza una nota de voz desde texto con el motor self-hosted de EasyBits (kokoro). Servicio de flota on-demand, sin proveedores externos. Devuelve un File público cuyo `audioUrl` puedes mandar al chat (con `send_message({url})` sale como nota de voz) o pasar a `avatar_video_create`.","group":"design"},{"name":"wait_for_document","description":"Block until a document's background generation stabilizes (no recent updates AND minimum sections present), or timeout. Use after clone_document or any tool that returns status:\"generating\" so you can respond once the doc is ready instead of polling get_document yourself.","group":"design"},{"name":"get_document_directions","description":"Generate 4 design directions for a document. Each direction includes fonts, colors, mood, and layout hints. Use one of the returned directions when calling generate_document.","group":"docs"},{"name":"replace_html","description":"PRIMARY tool for editing document pages. Use this for ANY targeted change — single sentence, color swap, one element, one attribute. Works like Claude Code's Edit (old_html → new_html). Cheap: only the diff travels, and the server returns {noop:true} when the replacement produces identical HTML (so it's safe to retry without triggering a re-deploy). Read with get_page_html first, copy the exact substring, then replace. Prefer over set_section_html — no CSS selector fragility. See get_docs(\"agent-editing\").","group":"docs"},{"name":"add_contact","description":"Add (or update) a contact in your audience. Tags let you target broadcasts. Upserts by email.","group":"email"},{"name":"create_broadcast","description":"Create a draft broadcast (newsletter) to send to your audience. Returns the broadcast id — send it with send_broadcast.","group":"email"},{"name":"list_broadcasts","description":"List your broadcasts (subject, status draft/sending/sent, total/sent/failed counts).","group":"email"},{"name":"list_contacts","description":"List your contacts (email, name, tags, status subscribed/unsubscribed/bounced). Optionally filter by tag.","group":"email"},{"name":"send_broadcast","description":"Send a draft broadcast to all subscribed contacts (filtered by its audienceTag). Skips unsubscribed/bounced contacts. Returns sent/failed counts.","group":"email"},{"name":"send_email","description":"Send a transactional email (HTML) from EasyBits. For one-off messages — use create_broadcast/send_broadcast for newsletters to a list.","group":"email"},{"name":"fleet_agent_capabilities","description":"Estado de configuración de un FleetAgent (lo mismo que GET /api/v2/fleet-agents/:id/capabilities): conectores (`capabilities` con `secretsPresent`), builtins, `groups` (config por canal; \"*\" = default del agente), `agent` (prompt/modelo/effort/buckets), `engines`, `models`, `buckets`, `skills`, `customMcps`. Úsalo antes de `fleet_agent_configure` para ver qué hay y verificar después.","group":"fleet"},{"name":"fleet_agent_configure","description":"Aplica UNA mutación de configuración a un FleetAgent — el mismo contrato que POST /api/v2/fleet-agents/:id/capabilities. `action` + `params` con los campos de esa acción. Por agente: set-agent-prompt{systemPrompt} · set-name{name} · set-model{model} · set-effort{effort} · set-engine{engine} · set-secret{name,value} (vault del owner) · add-mcp{name,url|pkg,requiredSecret?} (http → Authorization: Bearer <secret>) · remove-mcp{name} · add-skill{files[],name?,description?} · toggle-skill{skillId,on} · delete-skill{skillId} · toggle-own-number{on} · connect-teams · recycle-box. Por canal (params.groupId; \"*\" = todos): set-cap-level{cap,level:off|read|write} (ENCIENDE un MCP añadido con add-mcp — add-mcp solo lo registra) · toggle-builtin{builtin,on} · set-prompt{systemPrompt} · set-toolgroup{buckets[],inherit?} · set-tool-deny{tool,on} · toggle-asset{fileId,on} · set-db-allow{dbAllow[]}. Devuelve { ok:true, ... } o error.","group":"fleet"},{"name":"fleet_agent_list","description":"Lista los agentes de la FLOTA (FleetAgent: WhatsApp/WABA/web/Teams) de la cuenta. Distinto de `agent_list` (cajas persistentes). Devuelve { items:[{ id, name, assistantName, workerTemplate, hasOwnNumber, createdAt }], total }. Nunca expone el token del agente. Usa el `id` en `fleet_agent_capabilities` / `fleet_agent_configure`.","group":"fleet"},{"name":"fleet_agent_messages","description":"Historial de UNA conversación de un FleetAgent, con cursor. `groupId` es la conversación; `since` es el cursor de la última fila que ya viste (el webhook `turn.completed` trae el suyo) y devuelve SOLO lo posterior. Devuelve { items:[{id,role,text,sender,senderName,mediaUrl,createdAt,cursor}], cursor, hasMore, gap, gapReason?, resetAt? }. ⚠️ `gap:true` = no te fíes del delta, relee entero: `cursor_too_old` (tu cursor ya no sirve) o `context_reset` (tenemos el hilo pero el AGENTE perdió su memoria — su caja se perdió sin respaldo).","group":"fleet"},{"name":"create_backup","description":"Take a data backup right now instead of waiting for tonight's run. Copies the paths declared in runspec.dataPaths to durable off-host storage. Run this before anything risky.","group":"hosting"},{"name":"create_machine","description":"Buy an ALWAYS-ON machine (permanent VM, flat MXN/month). NO platform plan needed — hosting bills on its own. If the account has an active plan the machine is provisioned right away and rides that invoice; otherwise this returns { checkoutUrl } for the customer to pay, and the machine is provisioned automatically once payment clears (poll list_machines). Reserved CPU and performance-4x are by-request until enabled.","group":"hosting"},{"name":"delete_machine_release","description":"Delete a release and its stored artifact. Refuses if it is the release currently deployed on a live machine.","group":"hosting"},{"name":"deploy_machine","description":"Publish the machine's current app code as a versioned RELEASE in durable storage. This is what makes the box recoverable: with a release you can rebuild the machine anywhere (redeploy_machine) or roll back (rollback_machine). Requires a runspec. NOTE: a release captures CODE, not data — data is covered by backups.","group":"hosting"},{"name":"get_machine_logs","description":"The app's own log, last N lines: the systemd unit's journal when the runspec has a unit, otherwise the file the startCommand writes to. First stop when a deploy says it started but the site does not answer.","group":"hosting"},{"name":"get_machine_runspec","description":"Read a machine's runspec (how its app is built, started and backed up). Returns null if none is set yet.","group":"hosting"},{"name":"grant_access","description":"Delegate access over YOUR account to another EasyBits account by email. scopes=[\"machines\"] lets them operate (monitor/configure/repair) all your permanent machines from their own account; billing/release stay yours. Idempotent (merges scopes). Reserved scopes: files, dbs.","group":"hosting"},{"name":"launch_app","description":"Put an app in production in ONE call — the `fly launch` of EasyBits: provisions the machine, gets the code in, builds, starts it, exposes a public HTTPS URL, publishes a recovery release and (optionally) attaches the customer's domain. Returns { url, releaseId, domain.dns }. PREFER THIS over wiring create_machine + deploy_machine + expose + domain by hand: doing it manually, the step that gets skipped is the release, and a machine without a release cannot be rebuilt if it dies. NO platform plan needed: if the account has none, this returns { checkoutUrl } instead — give it to the customer, and once they pay the machine is created automatically (find it with list_machines, then launch again with its sandboxId). Pass exactly ONE source.","group":"hosting"},{"name":"list_access","description":"List the accounts you have delegated YOUR account to, and the scopes each holds.","group":"hosting"},{"name":"list_backups","description":"List a machine's daily data backups (newest first), with size, consistency level and expiry. Backups run nightly, are kept 7 days, and are included in the machine price.","group":"hosting"},{"name":"list_machine_releases","description":"List published releases, newest version first. Omit sandboxId to list every release in the account.","group":"hosting"},{"name":"list_machine_secrets","description":"Which env vars this app uses (`secretNames`) and what else is available in the account vault to wire up (`inVault`). Names only — a stored value is never readable again, not even by its owner.","group":"hosting"},{"name":"list_machine_templates","description":"List machine templates with their required env, so you know what `env` to pass to create_machine. Managed-runtime templates (e.g. ghostyclaw) list the keys they need (ANTHROPIC_API_KEY, NANOCLAW_ADMIN_TOKEN, …).","group":"hosting"},{"name":"list_machine_tiers","description":"List the always-on hosting catalog: tiers (nano…performance-4x) with vCPU/RAM/NVMe and flat MXN/month price (shared + reserved where available), plus the disk add-on price. Use to quote a machine before create_machine.","group":"hosting"},{"name":"list_machines","description":"List all always-on machines (permanent sandboxes) owned by the current account, with tier + monthlyMxn (status self-healed against the host).","group":"hosting"},{"name":"make_permanent","description":"Promote an existing (ephemeral) sandbox to an always-on machine: keeps the SAME sandboxId, disarms the host reaper, and starts flat MXN/month billing for the chosen tier. The 'spin up, then keep it' flow.","group":"hosting"},{"name":"redeploy_machine","description":"Build a BRAND NEW machine from a release: provisions a box, unpacks the code, builds and starts it. Two jobs, one path — recovering a machine that died, and CHANGING TIER (there is no in-place resize: you recreate at the new tier). Pass replaceSandboxId to release the old machine once the new one is confirmed running, or you pay for both. The new box starts with NO data — restore a backup after.","group":"hosting"},{"name":"release_machine","description":"Delete an always-on machine. DESTRUCTIVE AND IMMEDIATE: stops billing, takes a last backup and destroys the VM — there is no grace period and no undo. The backup is kept 7 days and is the only way back: list_backups, then restore_machine_from_backup onto a new machine. Requires confirm:true. Owner-only.","group":"hosting"},{"name":"restart_machine","description":"Restart the app process with the CURRENT secrets and runspec. Seconds: it downloads nothing and builds nothing — unlike rollback_machine (fetches a release tarball) or redeploy_machine (provisions a whole new box). Use it to close a batch of set_machine_secrets({restart:false}), or when the app is wedged but its code is fine.","group":"hosting"},{"name":"restore_machine","description":"Revive a machine that was suspended because its SUBSCRIPTION was cancelled (trial ended, card failed) — it sleeps for 7 days before being purged. Resumes the VM (data intact) and re-attaches billing. Owner-only. Does NOT apply to release_machine, which destroys immediately: for that, restore_machine_from_backup onto a new machine.","group":"hosting"},{"name":"restore_machine_from_backup","description":"Restore a backup's data into a machine, OVERWRITING what is there. Requires confirm:true. Restoring onto the source machine automatically takes a pre-restore backup first. Pass targetSandboxId to restore into a different box (e.g. one just built with redeploy_machine).","group":"hosting"},{"name":"revoke_access","description":"Revoke delegated access from an account by email. Omit scopes to revoke everything; pass scopes to remove only those. Idempotent.","group":"hosting"},{"name":"rollback_machine","description":"Put a previous release back on the SAME machine: download, extract and restart. No build: a release published by launch_app ships its build. Use after a bad deploy. Data is untouched.","group":"hosting"},{"name":"sandbox_admin","description":"Admin passthrough to a permanent machine's in-VM admin API (:8787) — WhatsApp pairing (/admin/whatsapp/status|link|unlink) + CLAUDE.md CRUD for managed-runtime machines (ghostyclaw). Owner OR a 'machines' delegate. `path` must start with /admin/.","group":"hosting"},{"name":"sandbox_domain_add","description":"Attach a custom domain to a sandbox port, served over HTTPS at https://<domain> with an auto-issued TLS cert — instead of the default sb-<id>-<port>.sandboxes.easybits.cloud URL. Works for BOTH subdomains (app.cliente.com → CNAME) and apex/root domains (cliente.com → A record). The response's `dns` field tells you the EXACT record to create (type/name/value): apex returns an A record, subdomains a CNAME. Expose/serve the port first; one domain maps to one sandbox. After the customer sets the DNS record, use sandbox_domain_verify to confirm it's live.","group":"hosting"},{"name":"sandbox_domain_list","description":"List the custom domains attached to a sandbox.","group":"hosting"},{"name":"sandbox_domain_remove","description":"Detach a custom domain from a sandbox.","group":"hosting"},{"name":"sandbox_domain_verify","description":"Check whether a custom domain is live: confirms DNS resolves and that https://<domain> serves with a valid TLS cert. Use after the customer sets the CNAME to tell them whether it's ready or what's still missing.","group":"hosting"},{"name":"sandbox_expose_port","description":"Expose a port running inside the sandbox as a public HTTPS URL (e.g. https://sb-<id>-<port>.sandboxes.easybits.cloud) — like E2B getHost / Daytona getPreviewLink. The unguessable sandboxId is the capability; anyone with the URL can reach the service. The URL is live while the sandbox is running. Start your server first (e.g. via sandbox_exec) then expose its port. Serves LAYER 7 over an auto-issued TLS cert: HTTP/HTTPS AND WebSocket — connect with wss:// on the SAME hostname, no tunnel (cloudflared etc.) and no raw port needed. What it does NOT do is raw layer 4, so 22/23/25/445/3389 are rejected — use sandbox_expose_raw_port for those. If the response carries a `warning` field, the service is listening only on 127.0.0.1 inside the box: the URL is published but will return 502 until you rebind it to 0.0.0.0 (or ::).","group":"hosting"},{"name":"sandbox_logs","description":"Read recent journald (systemd) logs from inside the sandbox — native log access instead of piping journalctl through sandbox_exec. Filter to one service with unit (e.g. 'ghosty-gc-runtime'); omit it for the whole journal. lines tails the last N (default 200, max 5000), since accepts a journalctl time spec (e.g. '10 min ago', '2026-06-23 12:00'), grep filters lines. Streaming/follow is NOT supported.","group":"hosting"},{"name":"set_machine_runspec","description":"Declare HOW to build and run the app on a machine: appDir (required), plus buildCommand/startCommand or a systemd unit, port, and dataPaths (what the daily backup captures). Required before deploy_machine. Merges with the existing runspec. Do NOT put secrets in env — it is stored and baked into every release tarball; use the vault instead.","group":"hosting"},{"name":"set_machine_secrets","description":"Give an app its environment variables: stores each value encrypted in the account vault AND wires the name into the machine's runspec so the app receives it as an env var when it builds and starts. This is what `secret_set` alone does NOT do — that one only stores the value. Accumulative: passing a new name does not unset the ones already in use. Values never enter the runspec or the release tarball, and can never be read back. TAKES EFFECT IMMEDIATELY: the app process is restarted (seconds, no build). The reply says so in `restarted` / `pendingRestart`.","group":"hosting"},{"name":"unset_machine_secret","description":"Stop injecting an env var into this machine. Restarts the app right away, so it loses the var NOW — if it needs it to boot, it will not come up. The value stays in the account vault, so it can be wired back with set_machine_secrets.","group":"hosting"},{"name":"create_document","description":"Create a new document. Pages (sections) are optional — add them later via update_document. Each section: { id, order, html, type?, name? }. If providing section html, follow page layout rules — call get_docs(\"document-design\"). SOCIAL FORMATS: pass `format.preset` for IG/LinkedIn carousels and Stories. Available presets: ig-feed (1080×1350, 4:5), ig-story / wsp-status / tiktok (1080×1920, 9:16), ig-square / fb-square (1080×1080, 1:1), li-feed (1080×1350), slide-16-9 (1920×1080), letter (default). Or pass custom width+height (100-10000px). Sets metadata.intent automatically (social/presentation/document). When generating HTML for non-letter formats, design content edge-to-edge to fill the entire frame — no letter-style margins/padding. BRAND KIT: if the user has a default brand kit, its colors/fonts/logo are auto-applied — write HTML with Tailwind semantic classes (bg-primary, bg-surface, text-on-surface, text-accent, font-heading, font-body) instead of hardcoding hex. Pass brandKitId to use a specific kit. TO CLONE A PDF: (1) upload_file the PDF, (2) pdf_to_images, (3) generate HTML per page using vision, (4) create_document with sections. TIP: For quotations/invoices use create_quotation — single-step tool.","group":"magnet"},{"name":"create_form","description":"Create a contact/subscription form for a website. Returns an HTML snippet.","group":"magnet"},{"name":"create_geo_scorecard","description":"Create a GEO (Generative Engine Optimization) scorecard from structured data. Returns the document + PDF.","group":"magnet"},{"name":"create_lead_magnet","description":"Create a complete lead magnet in ONE call: landing page + capture form + delivery to your content.","group":"magnet"},{"name":"create_screening_report","description":"Create a screening/compliance report (BlackPLD, PEP, sanctions) from structured data. Returns the document + PDF.","group":"magnet"},{"name":"create_website","description":"Create a new website with a name. Generates a slug automatically. Returns `{ website }` with id, slug, prefix, url.","group":"magnet"},{"name":"deploy_website_file","description":"Deploy a file to a website in a single call — no presigned URL or status update needed. Pass the file content directly (text or base64). Max 1MB. Returns `{ fileId, fileName, url }` — the file is immediately live at that `url`. Always embed the returned `url` verbatim in your HTML; do NOT construct URLs manually from `websiteId`/`fileName`.","group":"magnet"},{"name":"edit_geo_scorecard","description":"Edit an existing GEO scorecard with updated structured data. Rebuilds the HTML and regenerates the PDF.","group":"magnet"},{"name":"edit_screening_report","description":"Edit an existing screening report with updated structured data. Rebuilds the HTML and regenerates the PDF.","group":"magnet"},{"name":"get_default_brand_kit","description":"Get the user's default brand kit (colors, fonts, logo). Returns null if none is set. This kit is auto-applied when creating documents/quotations without an explicit brandKitId.","group":"magnet"},{"name":"get_node_outline","description":"Mapa DIRECCIONABLE de una página: cada nodo con su `data-id`, tag, clases y texto propio. Léelo ANTES de `patch_node` para saber a qué apuntar, en vez de traerte el HTML entero.","group":"magnet"},{"name":"get_page_html","description":"Get the full HTML and metadata of a single page. Lighter than get_document when you only need one page. Returns { id, name, html, order }.","group":"magnet"},{"name":"inject_html","description":"Inject HTML into an existing page on a website without rewriting the entire file.","group":"magnet"},{"name":"list_brand_kits","description":"List your brand kits (color palettes, fonts, logos for consistent document styling).","group":"magnet"},{"name":"list_form_submissions","description":"List submissions for a form. When formId is omitted, returns recent submissions across ALL your forms (use list_forms to discover form IDs).","group":"magnet"},{"name":"list_forms","description":"List all your forms with submission counts. Use this to discover form IDs before calling list_form_submissions.","group":"magnet"},{"name":"list_website_files","description":"List files belonging to a website. Returns `{ items, nextCursor, hasMore }`. When `hasMore` is true, pass `nextCursor` as `cursor` to get the next page.","group":"magnet"},{"name":"list_websites","description":"List your websites (paginated). Returns { items, nextCursor, hasMore, total }. When hasMore is true, pass nextCursor as offset to get the next page. Use search to filter by name.","group":"magnet"},{"name":"patch_node","description":"Edita una página POR NODO: reemplaza, borra o inserta usando el `data-id` como dirección. Lo que no tocas queda BYTE-IDÉNTICO.","group":"magnet"},{"name":"set_page_html","description":"Replace the ENTIRE HTML of a single page. Use ONLY for full-page rewrites or wholesale restructure. For ANY edit smaller than ~80% of the page (text change, color tweak, single element), use replace_html instead — it's faster, sends a fraction of the tokens, and the server skips re-deploy when the resulting HTML is unchanged. See get_docs(\"agent-editing\") for cost-efficient editing patterns.","group":"magnet"},{"name":"transform_image","description":"Crop, resize, rotate, flip, convert, or apply grayscale to an image. Creates a new file (original unchanged). Returns `{ file, originalSize, transformedSize, transforms }`.","group":"magnet"},{"name":"upload_website_file","description":"Upload a file to a website via presigned URL. Returns `{ file, putUrl }` — PUT the bytes to `putUrl`, then call `update_file(status: 'DONE')`. Files uploaded here are PUBLIC by default and safe to embed in published HTML. Best for binary/large files (>1MB) like images/video/PDFs. For text files <1MB (HTML/CSS/JS) prefer `deploy_website_file` which does everything in one call, and link those by RELATIVE path (`css/estilos.css`), not by storage URL — see MULTI-FILE SITES in `deploy_website_file`. PREFER THIS over `upload_file` for any asset that will appear in a published website. After the PUT+update_file handshake, embed the canonical `file.url` returned — do NOT construct URLs from `websiteId`/`fileName`. Public URLs start with `https://easybits-public.t3.storage.dev/`.","group":"magnet"},{"name":"create_payment_link","description":"Create a MercadoPago payment link (Checkout Pro). Returns a shareable initPoint URL. Requires the user to have connected their MercadoPago account at /dash/developer/payments. Money goes directly to the user's MP account; EasyBits never holds funds.","group":"payments"},{"name":"list_payment_links","description":"List your payment links (id, title, amount, status pending/paid, initPoint, payerEmail).","group":"payments"},{"name":"agent_create","description":"⚠️ DEPRECATED para cajas permanentes: usa `create_machine` (modelo único de caja — Sandbox permanente con billing + delegación; pasa `template` + `env`). `agent_create` se mantiene por compatibilidad (clientes existentes / ghosty-studio). Spawn a long-lived agent inside a Firecracker microVM and return its reachable agentUrl. Distinct from `agent_run` (Claude one-shot managed): this returns a persistent endpoint you can post messages to. DEFAULT y producto principal = 'rust-ghosty' (Ghosty: cerebro CodeWhale/Rust DeepSeek-first + canales web SSE y WhatsApp, con búsqueda web BrightData y catálogo easybits ya cableados) — spawnéalo con SOLO `agent_create({})` o `agent_create({name:'...'})`: easybits inyecta DeepSeek/runtime/easybits/admin del vault del dueño, NO necesitas `env`. Otros templates disponibles: 'computer-ghosty'/'computer-ghosty-gemini' = workstation computer-use (escritorio Linux XFCE que el agente maneja + terminal; tras 'running', `agent_list` da un `desktopUrl` noVNC público y un `terminalUrl`), y 'ghostyclaw' = daemon nanoclaw always-on (WhatsApp/Slack/Telegram). El `env` se inyecta al runtime al arrancar — nunca se hornea en la imagen. Returns { sandboxId, agentUrl, healthUrl }.","group":"sandbox"},{"name":"agent_destroy","description":"Destroy a persistent agent created via `agent_create` / `ghosty_spawn`: kills the underlying Firecracker sandbox AND deletes its registry row, so it disappears from `agent_list` and the dashboard. Idempotent — succeeds even if the sandbox was already gone (e.g. TTL expired). Prefer this over `sandbox_destroy`: the latter only frees the VM and leaves the agent registry orphaned (still shown as running). Pass the `agentId` from `agent_list`. Returns { ok: true }.","group":"sandbox"},{"name":"agent_install_skill","description":"Install / upload a skill (Agent Skills Open Standard — a SKILL.md with frontmatter `name`, `description`, `tools` + optional bundled assets) onto a persistent agent. The skill is hot-loaded inside the microVM — the next `agent_message` invocation picks it up without restart. Supported templates today: openclaw, ghostyclaw. Returns { ok, name, path, files, bytes }.","group":"sandbox"},{"name":"agent_list","description":"List the persistent agents (chat-*, ghostyclaw, openclaw) owned by the calling account. Use this to enumerate agents before messaging, installing skills, or destroying. Returns AgentRecord[] with agentId, template, name, status, sandboxId, agentUrl, embedToken, createdAt and expiresAt.","group":"sandbox"},{"name":"agent_message","description":"Send a chat message to a persistent agent created via `agent_create` / `ghosty_spawn` (chat-* templates) and return the assembled response. Internally proxies through sandbox-host (the microVM's IP isn't routable from outside) and consumes the SSE stream into a single string. For real-time token streaming from a browser, use the public /api/v2/agents/:id/message endpoint with the embedToken instead. Returns { content, tokens, usage? } — `usage` {inputTokens, outputTokens, totalTokens} llega cuando el agente lo reporta (ACP: ghosty-lite / goose) y son totales de la SESIÓN, no del turno; `tokens` cae al conteo de chunks si no hay usage.","group":"sandbox"},{"name":"agent_record","description":"ONE-SHOT: record a computer-use agent (template computer-ghosty-gemini) while it performs a task, and return a public URL to the resulting mp4. Internally: starts screen recording, sends `prompt` to the agent and waits for the turn to finish, stops recording, and returns { url }. This is the simplest way to get 'the agent did X' as a shareable video — one call. The URL is a static mp4 served from the VM's already-public desktop host; it's live while the agent's sandbox is running (capability = the unguessable subdomain). For multi-turn clips, use agent_recording_start / agent_recording_stop instead.","group":"sandbox"},{"name":"agent_recording_list","description":"List the desktop recordings stored on a computer-use agent's VM. Returns an array of { id, url, bytes, at, recording } sorted newest-first; each `url` is a public link to the mp4 (live while the sandbox runs).","group":"sandbox"},{"name":"agent_recording_start","description":"Start recording the desktop of a computer-use agent (computer-ghosty-gemini). ffmpeg captures display :0 to an mp4 inside the VM. One recording at a time per agent; calling again while recording returns the in-progress one. Returns { recording, id, startedAt, url } — the url becomes a playable mp4 after agent_recording_stop. Drive the agent with agent_message between start and stop, then stop to finalize.","group":"sandbox"},{"name":"agent_recording_stop","description":"Stop the in-progress desktop recording and finalize the mp4. Returns { recording:false, id, url, bytes, durationMs } where `url` is a public link to the playable mp4 (served from the VM's desktop host, live while the sandbox runs).","group":"sandbox"},{"name":"agent_run","description":"Start a managed Claude agent (Claude Agent SDK loop) inside a fresh Firecracker microVM. By default the agent has Bash/Read/Write/Edit/Glob/Grep/WebFetch (interactive/session tools like Agent, AskUserQuestion, Skill, TodoWrite are denied — this is a headless VM) and full root with open internet egress. EasyBits supplies Anthropic credentials by default (OAuth/Max-plan token preferred, API key fallback) and bills the run; switch to BYOK (Bring Your Own Key) by registering your own credential via `secret_set` and passing it in secrets[] — supported names: \"CLAUDE_CODE_OAUTH_TOKEN\" (Max plan), \"ANTHROPIC_API_KEY\" (pay-per-token). With BYOK Claude tokens are billed by Anthropic directly to your account; EasyBits charges 0 for tokens. Pass other entries in `secrets: [...]` to inject EasyBits Secrets as env vars in the sandbox — spawned MCP children (brightdata, easybits, etc.) inherit them via process.env. ASYNC: returns { jobId, status:'running' } immediately. Poll with `agent_run_status({ jobId })` until status is 'done'/'error'/'expired'. Sandbox auto-destroys at 30 min TTL — that's the real cap on a runaway loop. Default: claude-sonnet-4-6, no turn cap unless you pass max_turns.","group":"sandbox"},{"name":"agent_run_destroy","description":"Destroy the sandbox underlying an agent run. Call this after you've successfully fetched the result via `agent_run_status` to free resources eagerly. If you don't call it, the sandbox auto-destroys at its 30-min TTL.","group":"sandbox"},{"name":"agent_run_status","description":"Poll an agent run started with `agent_run`. Returns the current status: 'running' (still working), 'done' (success — result + steps + usage included), 'error' (failed — error + log included), or 'expired' (sandbox auto-destroyed at TTL before the result was fetched). IDEMPOTENT: safe to call repeatedly — billing fires exactly once and the sandbox is left running until you call `agent_run_destroy` or its 30-min TTL expires.","group":"sandbox"},{"name":"call_create","description":"Crea una videollamada online y devuelve el link para compartir — no necesita nada previo. Levanta el servidor de llamadas, espera ~15s a que arranque, configura los puertos WebRTC y retorna { sandboxId, room, roomUrl }. Comparte roomUrl con los participantes. Usa sandboxId con call_record y call_stop para grabar. Ideal para: 'arma una llamada', 'crea una sala de video', 'quiero hacer una entrevista'.","group":"sandbox"},{"name":"call_destroy","description":"Termina una videollamada limpiamente: (1) para la grabación activa si la hay y sube el MP4 a Files, (2) rescata cualquier grabación huérfana en la VM y la sube, (3) destruye el servidor. Llama esto cuando la llamada haya terminado para liberar recursos. Si no se llama, el servidor se auto-destruye al TTL (6h).","group":"sandbox"},{"name":"call_files","description":"Lista las grabaciones y transcripts de todas las llamadas — archivos permanentes en EasyBits Files, disponibles aunque la VM ya se haya destruido. Retorna [{ id, name, url, source, createdAt }].","group":"sandbox"},{"name":"call_record","description":"Inicia la grabación de una llamada en curso. Un chromium headless se une como observador invisible y ffmpeg captura el layout completo (cámaras + pantalla compartida) en 1080p. Una grabación activa por sandbox. Retorna { recording: true, id, room, startedAt }.","group":"sandbox"},{"name":"call_status","description":"Estado del servidor de llamadas: si hay grabación activa y quiénes están en la sala. Retorna { recording, room, startedAt, participants[] }. Útil para saber si la llamada sigue viva antes de grabar o destruir.","group":"sandbox"},{"name":"call_stop","description":"Detiene la grabación, sube el MP4 permanentemente a los Files del usuario y genera un transcript con Whisper. Retorna { url, fileId } — `url` es el enlace permanente al MP4 (sobrevive aunque se destruya la VM). El .txt del transcript también queda en Files.","group":"sandbox"},{"name":"call_transcript","description":"Devuelve el TEXTO del transcript de una llamada, inline, listo para resumir o extraer pendientes — no un link. Incluye un `status` honesto: `transcribing` (Whisper aún procesando, reintenta en ~1 min), `ready` (texto disponible en `text`), `failed` (la grabación no tuvo audio o Whisper falló), `unavailable` (hay grabación pero el transcript no se guardó), `no_recording` (nadie grabó la llamada). Pasa `sandboxId` para el estado EN VIVO durante/tras la llamada; sin él devuelve el transcript más reciente de tus Files.","group":"sandbox"},{"name":"delete_snapshot","description":"Delete a sandbox snapshot, freeing its stored image. Does not affect the source box or any children already forked from it.","group":"sandbox"},{"name":"ghosty_spawn","description":"Spawn THE default Ghosty agent — zero configuration. Uses EasyBits' managed Anthropic credentials (same billing pattern as agent_run: tokens charged via AiGenerationLog), Haiku 4.5 model, and a generic Ghosty system prompt. For when the caller just wants 'an agent' without choosing template, model, or providing keys. Persistent runtime (chat-anthropic, ~256MB). Override via systemPrompt if you want a non-default persona. Returns { sandboxId, agentUrl, healthUrl }.","group":"sandbox"},{"name":"goose_spawn","description":"Spawn THE managed Goose agent — zero configuration. Block's open-source coding agent running as ACP server (JSON-RPC over SSE) inside a Firecracker microVM. Uses EasyBits' managed Anthropic credentials (Haiku 4.5). Returns { sandboxId, agentUrl, healthUrl, agentId, embedToken }. systemPrompt is currently ignored for Goose (uses Goose default).","group":"sandbox"},{"name":"list_snapshots","description":"List your saved sandbox snapshots (copy-on-write clone sources): snapshotId, name, source box, size, created date.","group":"sandbox"},{"name":"sandbox_apply_patch","description":"Atomic hotfix in one call: apply N surgical edits, then optionally rebuild and restart the service. Edits run first and sequentially (a failing edit aborts before any build). If rebuild is given and the build exits non-zero, the restart is SKIPPED so your running daemon stays up. Returns { applied, buildOutput, buildExitCode, restarted, status }. Use this to ship a fix to a running box without juggling write+exec+restart yourself.","group":"sandbox"},{"name":"sandbox_create","description":"Spawn a Firecracker microVM sandbox. Returns sandboxId used for subsequent calls. Base templates: ubuntu, python, node, bun. Agent/harness templates: node-agent, claude-code, goose, ghostyclaw, openclaw, chat-openai, chat-anthropic (for the chat-* persistent runtimes prefer agent_create). Call templates_list for the full catalog with required env. Default timeout 300s; max session length depends on your plan (Byte 1h · Mega 4h · Tera 24h) — sandbox auto-destroys when timeout elapses (extend with sandbox_extend).","group":"sandbox"},{"name":"sandbox_destroy","description":"Permanently destroy an EPHEMERAL sandbox and free its resources. Idempotent. (Permanent machines are released via release_machine, which soft-deletes.)","group":"sandbox"},{"name":"sandbox_exec","description":"Execute a shell command inside a sandbox. Returns stdout, stderr, exitCode, durationMs. Default timeout 60s, max 600s.","group":"sandbox"},{"name":"sandbox_exec_background","description":"Start a long-running command in the background. Returns { execId, status } immediately instead of blocking like sandbox_exec. Poll with sandbox_exec_status; list with sandbox_exec_list; stop with sandbox_exec_kill. Use for dev servers / long tasks — pair with sandbox_expose_port to reach a server it starts. Do NOT try to background a command inside sandbox_exec with nohup or &: sandbox_exec is a synchronous call and its shell dies with the response, taking the child with it. Prefer writing the command as `exec <program>` (e.g. 'exec node server.mjs'): the shell is REPLACED by your process instead of staying as its parent, so logs and signals go straight to it.","group":"sandbox"},{"name":"sandbox_exec_kill","description":"Kill a background command started with sandbox_exec_background. Signals the whole process GROUP (SIGTERM, then SIGKILL after a grace period), so a dev server's forked children die too instead of surviving and holding the port. Killing an already-finished command is a success, not an error: it returns ok with alreadyExited.","group":"sandbox"},{"name":"sandbox_exec_list","description":"List the background commands of a sandbox: execId, command, status, exit code and log sizes. This is how you recover an execId you lost — without it a running process cannot be polled or killed. Returns sizes, not the log bodies; use sandbox_exec_status for those.","group":"sandbox"},{"name":"sandbox_exec_status","description":"Poll a background command started with sandbox_exec_background. Returns status (running/exited), exitCode (when exited), and the captured stdout/stderr tail (last 1MB each).","group":"sandbox"},{"name":"sandbox_expose_raw_port","description":"Expose a RAW layer-4 (TCP/UDP) port of the sandbox — the only way to reach a service that does not speak HTTP (SSH on 22, media/UDP). NOT needed for WebSocket: wss:// works over sandbox_expose_port, which serves layer 7 (HTTP + WS) and rejects only 22/23/25/445/3389. Returns { hostPort, guestPort, protocol, host, endpoint }: dial `endpoint` (\"<host>:<hostPort>\") verbatim — the host port comes from a pool (49000-49999), is DIFFERENT per box and is NOT equal to the guest port. It is also not stable: it is released when the box is destroyed, so read it again instead of remembering it. Capability-gated by template: a 403 means this template has no such raw port (permanent — do NOT retry).","group":"sandbox"},{"name":"sandbox_extend","description":"Refresh a sandbox's TTL before the auto-destroy reaper fires. No-op on persistent boxes (returns { persistent, noop }). Total remaining lifetime is capped by your plan's max session length (Byte 1h · Mega 4h · Tera 24h).","group":"sandbox"},{"name":"sandbox_files_delete","description":"Delete a file or directory inside the sandbox. Pass recursive=true to remove a non-empty directory.","group":"sandbox"},{"name":"sandbox_files_edit","description":"Surgically edit a file inside the sandbox: replace oldString with newString in place (read → replace → write). Use this instead of sandbox_exec + sed/echo — it sidesteps shell-escaping entirely. By default replaces ALL occurrences and returns the count; pass replaceAll=false to change only the first match (and to fail when oldString is ambiguous). Errors if oldString is not found.","group":"sandbox"},{"name":"sandbox_files_list","description":"List directory entries (name, size, isDir, modifiedAt) inside the sandbox.","group":"sandbox"},{"name":"sandbox_files_mkdir","description":"Create a directory (and any missing parents) inside the sandbox.","group":"sandbox"},{"name":"sandbox_files_move","description":"Move or rename a file/directory inside the sandbox. Parent directories of the destination are created if needed.","group":"sandbox"},{"name":"sandbox_files_read","description":"Read a file from the sandbox. Small text (<50KB) returns inline JSON with full content. Recognized images (PNG/JPEG/GIF/WebP) under 1MB return as MCP image blocks. Larger or binary files upload to platform storage and return a 7-day signed URL plus metadata — this prevents context-window blow-up in the consuming agent. Use encoding=base64 for binary.","group":"sandbox"},{"name":"sandbox_files_write","description":"Write a file inside the sandbox. Creates parent dirs if needed. Use encoding=base64 for binary content.","group":"sandbox"},{"name":"sandbox_fork","description":"Boot N copy-on-write children from a sandbox (fresh IP each, collision-free), to explore variations in parallel. Pass sandboxId to snapshot a live box then fork it (Morph-style branch), OR snapshotId to fork an existing snapshot. count 1–16 (default 1). Children are ephemeral (auto-reaped at TTL) and count against your concurrent-sandbox budget. Returns the child records (still starting — poll sandbox_status until running).","group":"sandbox"},{"name":"sandbox_git_checkout","description":"Switch branches in a sandbox repo. With create:true it uses `checkout -B`, which is idempotent — safe to run on every wake-up without failing on 'branch already exists'.","group":"sandbox"},{"name":"sandbox_git_clone","description":"Clone a git repo into a sandbox. For private repos pass `token` (ideally as '$secret:NAME' from the vault) — credentials are used for this call only and are NEVER written to .git/config or visible in `ps`. Use `commit` to check out a specific sha (detached HEAD); otherwise the clone is shallow by default.","group":"sandbox"},{"name":"sandbox_git_commit","description":"Stage and commit in a sandbox repo. Stages everything by default; pass `paths` to stage a subset. If there is nothing to commit this returns { nothingToCommit: true } as a SUCCESS — it is not an error, so don't retry. Author identity is per-call and leaves no `git config` behind.","group":"sandbox"},{"name":"sandbox_git_log","description":"Commit history of a sandbox repo, paginated. Returns { items, nextCursor, hasMore } — pass nextCursor back as `cursor` for the next page.","group":"sandbox"},{"name":"sandbox_git_pull","description":"Pull into a sandbox repo. Fast-forward only by default (pass rebase:true to rebase). Returns any conflicted paths so the agent can act on them instead of guessing.","group":"sandbox"},{"name":"sandbox_git_push","description":"Push a sandbox repo to its remote. Pass `token` (or '$secret:NAME') for private repos. `force` uses --force-with-lease, so a push that would discard someone else's commits fails instead of silently winning.","group":"sandbox"},{"name":"sandbox_git_status","description":"Structured git status for a repo in a sandbox: branch, upstream, ahead/behind counts, and the staged/modified/untracked/conflicted file lists. Parsed from porcelain=v2, so it is stable across git versions and locales.","group":"sandbox"},{"name":"sandbox_kernel_restart","description":"Restart the persistent Jupyter kernel for a 'code-interpreter' sandbox — clears all state (variables, imports). Use to start fresh without recreating the sandbox.","group":"sandbox"},{"name":"sandbox_list","description":"List all active sandboxes owned by the current account.","group":"sandbox"},{"name":"sandbox_resume","description":"Restore a suspended sandbox from its snapshot (same TAP/IP/MAC/rootfs/volumes). The lifetime that remained when it was suspended is restored and the auto-destroy timer is re-armed — no need to call sandbox_extend. Returns the updated record with the new expiresAt.","group":"sandbox"},{"name":"sandbox_run_cell","description":"Run code in the sandbox's PERSISTENT Jupyter kernel — variables, imports and loaded data survive between calls (unlike sandbox_run_code, which is a fresh process each time). Requires a sandbox created with template='code-interpreter'. Returns stdout/stderr plus rich results; matplotlib charts (image/png) come back as native image blocks so the model can see them.","group":"sandbox"},{"name":"sandbox_run_code","description":"Run a snippet of Python/Node/Bash inline (no need to write a file first). Output captured. Default lang=python. NOTE: each call runs a FRESH process (python3 -c / node -e / bash -c) — there is NO persistent kernel, so variables do NOT survive between calls. Put dependent lines in a single call, or persist state to a file with sandbox_files_write. For stateful Python across cells (and matplotlib charts), use sandbox_run_cell on a 'code-interpreter' sandbox.","group":"sandbox"},{"name":"sandbox_runtime","description":"Control the service daemon running inside the sandbox via systemd — no manual systemctl/npm through sandbox_exec. action='status' shows a unit's state (or lists running services if unit omitted); 'restart' restarts a unit (unit required); 'rebuild' runs buildCommand in cwd then restarts unit (if given). Build runs with a 10-min timeout.","group":"sandbox"},{"name":"sandbox_set_bootstrap","description":"Declare the BOOTSTRAP of a sandbox: a script the host runs EVERY TIME the box wakes up, before the agent gets its first message. This closes a gap nobody else covers — a box restored from a snapshot revives with NO boot (no systemd, no entrypoint, no .bashrc), so one that slept three days wakes up with three-day-old code and nothing says so. Typical use: `git fetch && git checkout -B sesion/$EB_SANDBOX_ID origin/main`, or linking skills/AGENTS.md into the workdir. Make it idempotent (use `checkout -B`, not `-b`) — it runs on every wake. Env available: EB_RESUME=1, EB_SANDBOX_ID. Runs in /data/work. ⚠️ NEVER put a credential in the script: the recipe travels in metadata and shows up in listings. Pass an empty script to turn it off.","group":"sandbox"},{"name":"sandbox_snapshot","description":"Capture a named, persisted copy-on-write image of a RUNNING sandbox WITHOUT stopping it (the box keeps running). The snapshot can later be forked into N independent children with sandbox_fork — use it to freeze a known-good state (deps installed, project set up) and branch parallel experiments from it. Returns the snapshot record (snapshotId, sizeBytes).","group":"sandbox"},{"name":"sandbox_ssh_disable","description":"Close the SSH forward (port 22) of a sandbox. The key stays injected and sshd keeps running inside the box — it just becomes unreachable from outside. To actually revoke someone, remove their key from /app/secrets.env.","group":"sandbox"},{"name":"sandbox_ssh_enable","description":"Give the user SSH into the box, in one call: injects their public key(s) and restarts the box sshd. HAND THE USER `tunnel.setup` THEN `tunnel.command` — that path rides 443, so it works from offices and corporate VPNs where a high port silently fails and reaches you as an unreproducible 'it won't connect'. The top-level `command` uses a pool port: offer it only as a fallback, and never write its port into docs or remember it across sessions (it is neither 22 nor stable — call this again to read it back). The box's sshd is fail-closed: without a key it does not run at all, which is why the key must go in first. Access is key-only, as root. Only templates that declare port 22 work (dev-box is the one to recommend to users) — a 403 means this template has no SSH and is a permanent answer, do NOT retry.","group":"sandbox"},{"name":"sandbox_status","description":"Get a sandbox's record: status (starting/running/stopped/error/lost/suspended), template, createdAt, expiresAt, and metadata.","group":"sandbox"},{"name":"sandbox_suspend","description":"Snapshot a sandbox to disk and free its CPU/IP — the box stops billing compute but keeps its state. The TTL is PAUSED while suspended: the remaining lifetime is saved and restored on sandbox_resume, so you do NOT need to call sandbox_extend afterward. Restore with sandbox_resume.","group":"sandbox"},{"name":"sandbox_unexpose_raw_port","description":"Tear down a raw TCP/UDP forward created with sandbox_expose_raw_port. The host port returns to the pool and may be re-assigned to another box.","group":"sandbox"},{"name":"service_start","description":"Levanta un servicio de flota on-demand y devuelve sus URLs. kind='voice' arranca una caja con STT (whisper) + TTS (kokoro): retorna { sandboxId, transcribeUrl, speakUrl }. Para transcribir: POST el audio (bytes) a transcribeUrl → { text }. Para sintetizar voz: POST { text } a speakUrl → bytes de audio. Idempotente: si ya hay una caja de ese tipo corriendo, la reusa. La caja se auto-destruye tras ~10 min sin uso; usa service_stop para liberarla antes.","group":"sandbox"},{"name":"service_status","description":"Estado de un servicio de flota: si está corriendo y sus URLs. Pasa kind (p.ej. 'voice') o el sandboxId devuelto por service_start. Retorna { sandboxId, kind, status, urls, transcribeUrl?, speakUrl? } o null si no hay ninguno.","group":"sandbox"},{"name":"service_stop","description":"Detiene y destruye una caja de servicio de flota para liberar recursos. Pasa el sandboxId devuelto por service_start. Si no se llama, la caja se auto-destruye por idle (~10 min) o al TTL.","group":"sandbox"},{"name":"templates_list","description":"List available sandbox templates with metadata for catalog UI: tier (chat-embed/coding-harness/autonomous/custom/base), display name, description, required env vars and connection modes. Used by ghosty.studio to render dynamic 'new agent' forms. Pass tier= to filter (e.g. tier=chat-embed for embeddable chatbots only).","group":"sandbox"},{"name":"discover_tools","description":"Search the FULL Easybits MCP catalog — including tools NOT loaded in your current session's toolGroup. Returns name, description, inputSchema (JSON Schema) and `loaded` (whether it's already in tools/list). After finding what you need, call it via `run_tool({ name, params })` — no reconnect required. Combine with `run_tool` to escape the active toolGroup without restarting Claude.","group":"scripting"},{"name":"get_file","description":"Get file metadata and a signed download URL. Returns file object with a `readUrl` field containing a presigned GET URL (expires in 1h). Pass `fileId`, or `url` when all you have is a public/CDN URL of your own file (resolves it back to the file — useful to REUSE an existing file instead of re-uploading the same bytes).","group":"scripting"},{"name":"list_files","description":"List your files (id, name, size, contentType, access, status, createdAt). Returns `{ items, nextCursor, hasMore }`. When `hasMore` is true, pass `nextCursor` as `cursor` to get the next page. Excludes deleted files.","group":"scripting"},{"name":"run_tool","description":"Dispatch any Easybits tool by name — even tools NOT loaded in the active toolGroup. Use this after `discover_tools` to invoke a tool without reconnecting. `params` must match the tool's inputSchema (run discover_tools first to inspect it). Returns the tool's normal response shape.","group":"scripting"},{"name":"upload_file","description":"Create a generic user file record and get a presigned upload URL. Returns `{ file, putUrl }`. Upload bytes via PUT to `putUrl`. The file is created with status DONE immediately.","group":"scripting"},{"name":"get_website","description":"Get a website by ID with stats. Returns website object with url.","group":"sites"},{"name":"update_website","description":"Update a website's name or status. Stats (fileCount, totalSize) are recomputed automatically from the database.","group":"sites"},{"name":"add_video_scene","description":"Add a scene. html = the scene's markup (absolutely positioned to fill the frame; reference bundled media as assets/<name>; put scoped CSS in an inline <style>). timeline = optional GSAP snippet against a pre-declared paused `tl` (animate the scene's own elements). durationSec defaults to fit narration if given. narration = optional voiceover text (kokoro em_santa) muxed at the scene's start. afterIndex = 0-based insert position; omit to append.","group":"video"},{"name":"attach_video_asset","description":"Register a named media asset (image/logo/etc.) the render box downloads into assets/. Reference it in scene html as assets/<name>.","group":"video"},{"name":"avatar_video_create","description":"TALKING HEAD video generator. Powered by fal.ai (SadTalker / Hallo2 / LivePortrait — OSS models, pay-per-use). Takes a portrait image + audio and returns an mp4 with the face mouthing the words in sync with the audio.","group":"video"},{"name":"character_delete","description":"Delete a saved character. Only removes the Character record — the reference photos (if stored as Files) stay in the user's library.","group":"video"},{"name":"character_list","description":"List the user's saved characters (reusable identities for video generation). Call this when the user references someone by name and you need to know if they've been saved before.","group":"video"},{"name":"character_remember","description":"Save a reusable character (person, pet, mascot) from 1–3 reference photos. The character's slug can be used later in `video_create` to keep the same face/look across multiple generations. Call this once when the user first introduces someone they'll want to see repeatedly. Returns { id, name, slug } — the slug is what Ghosty should remember.","group":"video"},{"name":"create_video_project","description":"Create a stateful video project — an ordered set of animated scenes that compile to an MP4. Doc-style: create empty (or with scenes), then add/edit/reorder scenes, set music, and render_video_project. Portrait 1080×1920 by default (format presets: portrait/story/reel/tiktok = 9:16, square = 1:1, landscape/youtube = 16:9). Each scene is a self-contained HyperFrames sub-composition: you provide the scene MARKUP (html, absolutely-positioned, assets referenced as assets/<name>) and an optional GSAP timeline snippet against a pre-declared paused `tl` (e.g. tl.from('#title',{opacity:0,y:40,duration:0.6})). Add narration text per scene → synthesized with kokoro (voice em_santa) and muxed automatically at render.","group":"video"},{"name":"delete_video_project","description":"Delete a video project permanently.","group":"video"},{"name":"delete_video_scene","description":"Delete a scene by id.","group":"video"},{"name":"generate_captions","description":"Enqueue viral MrBeast-style animated captions for a video. Input: a public HTTPS URL to an MP4 / MOV / any common video format (iPhone HEVC supported). The service transcribes via Whisper, marks keywords + emojis with Claude Haiku, and renders 1080x1920-ish vertical (or matching source aspect) MP4 with burned-in animated captions (Bangers font, thick stroke, drop-shadow sticker, scale-overshoot pop, instant color switch). ASYNC: returns { jobId, status } immediately; poll get_caption_status until status is 'done' (then result.outputUrl) or 'error'. Typical render time: 30s–5min depending on clip length.","group":"video"},{"name":"get_caption_status","description":"Poll a captions render job started by generate_captions. Returns { status, elapsedMs, result?, error?, logs }. Status values: 'pending' (queued) | 'running' (in progress) | 'done' (result.outputUrl populated) | 'error' (error populated). Jobs are kept for 1h after completion. Recommended polling interval: 10–20s.","group":"video"},{"name":"get_video_project","description":"Get a video project with its full scene list (html, timeline, narration, durations) and assets.","group":"video"},{"name":"list_video_projects","description":"List the caller's video projects (newest first).","group":"video"},{"name":"list_videos","description":"List the user's video generations (latest first). Returns status, prompt, and file ids for each.","group":"video"},{"name":"render_video_project","description":"Compile the project to a HyperFrames composition and render it to MP4 on the owner's on-demand box (synthesizing any pending narration first). Synchronous — a short video takes tens of seconds. Returns { status:'ready', file:{ fileId, url, renderMs } }.","group":"video"},{"name":"reorder_video_scenes","description":"Reorder all scenes. sceneIds must list every existing scene id exactly once in the desired order.","group":"video"},{"name":"set_video_music","description":"Set (or clear) the continuous background music track. Pass a public audio URL; the box downloads and muxes it (auto-ducked under narration). Pass url:null to remove.","group":"video"},{"name":"set_video_scene","description":"Edit a scene by id (from get_video_project scenes[].id). Only pass the fields you change. Changing narration re-synthesizes the voiceover on next render.","group":"video"},{"name":"update_video_project","description":"Update project-level fields (name, theme, customColors, fps, width, height). Does NOT touch scenes.","group":"video"},{"name":"video_create","description":"Generate a short video (2–10s) from a prompt. Automatically orchestrates: (1) cinematic prompt enhancement, (2) still generation — with character references if `character` is provided, (3) animation via Gen-4.5. Returns the final mp4 as a File in the user's library.","group":"video"},{"name":"research_scrape","description":"[deprecated → web_fetch] Lee UNA página web y devuelve su contenido, aunque el sitio bloquee bots (IPs residenciales, JS resuelto). Cuesta 1 consulta.","group":"web"},{"name":"research_search","description":"[deprecated → web_search] Busca en Google (o Bing/Yandex/DuckDuckGo) y devuelve resultados estructurados: orgánicos, snack pack (negocios locales), knowledge panel, FAQs. Cuesta 1 consulta.","group":"web"},{"name":"web_crawl","description":"Lee una página y SIGUE sus links internos (mismo dominio) hasta `maxPages`, devolviendo markdown por página. Cobra 1 consulta por página realmente leída.","group":"web"},{"name":"web_extract","description":"Extrae REGISTROS estructurados (JSON con esquema estable) de una fuente conocida: Google Maps (negocios con teléfono/WhatsApp/web/rating), Mercado Libre, Amazon, Instagram, TikTok, LinkedIn, Facebook, YouTube, Indeed, Trustpilot, inmuebles24… Cobra 1 consulta POR REGISTRO devuelto (no por intento).","group":"web"},{"name":"web_extract_status","description":"Estado de un job de `web_extract`. Mientras corre: { status:'running' } (gratis). Cuando termina: { status:'done', items:[…], total } y se cobra 1 consulta por registro, una sola vez — volver a pedirlo no cobra de nuevo.","group":"web"},{"name":"web_fetch","description":"Lee UNA página web y devuelve su contenido, aunque el sitio bloquee bots (IPs residenciales, JS resuelto). Cuesta 1 consulta.","group":"web"},{"name":"web_search","description":"Busca en Google (o Bing/Yandex/DuckDuckGo) y devuelve resultados estructurados: orgánicos, snack pack (negocios locales), knowledge panel, FAQs. Cuesta 1 consulta.","group":"web"}]}