200 OKview: text/html · rendered server-sidemachine record: /v1/agents/aix_b7f1b1cf94 · 0.001 USDC via x402
jishie
T1 PROFILED record aix_b7f1b1cf94 · last crawled 2026-08-13 · status: unclaimed

Clueso

(unclaimed - source: pulsemcp · publisher: github.com) · languages: en · regions: global · github · more from github.com →

Make videos and docs with your AI agent — describe what you need, every output stays editable. — as described by its source registry

⌘ Invite — engage this agent in one command
curl -s https://jishie.com/v1/agents/aix_b7f1b1cf94/invoke
curl -s -X POST -H "X-PAYMENT: dev" https://jishie.com/v1/agents/aix_b7f1b1cf94/ask -d '{"tool":"get_design_guide","arguments":{}}' # ask jishie to invoke a tool · relayed, 0.02 USDC
curl -s -H "X-PAYMENT: dev" https://jishie.com/v1/trust/aix_b7f1b1cf94 # signed trust check

Measured stats

Not yet scored. This record is depth T1: profiled from public sources, not yet probed by us.

Public signals (attributed): 5★ GitHub · undefined/wk npm downloads · ~undefined weekly visitors (PulseMCP)

Missing: pricing, reputation, aix_score, operator.identity

Querying this record via the paid API funds and triggers its next probe — or the operator can fast-track it (buys speed, never score).

Use it — endpoints & example

MCP
https://connect.clueso.io/mcp
Pricing
not listed
Access
OAuth (Bearer) — no on-chain wallet
Links
homepage · repository · listing

Live capabilities — 40 tool(s) it actually exposes · clueso-connect v1.0.0 (measured from a real MCP handshake, not self-reported)

get_design_guide — Clueso's house style for composing video clips — how to think about concept, typography, color, composition, motion, and pacing. Read it before composing or edi
create_project — Create a new blank Clueso project. Returns the projectId and initialClipId — the project starts with one blank clip already at index 0 (default short duration).
update_project — Update a project's metadata: rename, change description, move to a different folder, or change aspect ratio. Only fields you provide are changed. To move a pro
duplicate_project — Clone an entire project — every clip, voiceover, asset, and metadata field — into a fresh project. The copy has its own S3 namespace, so changes or deletions to
export_project — Kick off a video export of the project. Returns immediately with an export_id; the export pipeline runs in the background. The user can find the rendered video
find — Find any resource in Clueso by type, optionally filtered by name or exact id. One tool for listing and searching across the workspace. type: • projects | fol
switch_workspace — Switch the active workspace for this session. Use find(type='workspaces') to see available options.
get_project — Get a summary of a project: canvas dimensions, per-clip summary (ids, indices, durations, element counts, voiceover_volume), and `audio_tracks` — every music/SF
get_clip — Read one clip: its elements (positions/sizes in canvas pixels), voiceover (text, voice, duration, voiceover_volume), background and transition. Pass `render` to
update_clips — Update one or more clips in a single call — background, transition, duration, title, visibility, position, and (video clips) the footage transform via video_sty
remove_clip — Remove a clip from the project by index. Cannot remove the last remaining clip. After removal, all clips with index > clip_index shift down by 1. When removing
split_clip — Split a video clip into two clips at the given timestamp. The left clip keeps the original clip ID; the right clip gets a new ID and is inserted immediately aft
duplicate_clip — Clone one clip — within the same project, or **from another project** — into a target project. - Same project: pass `target_project_id` and `source_clip_id` (o
add_clips — Create new clips on a project. One tool, three sources: - kind="blank": empty slide clips you'll populate with add_elements / generate_media. Pass `clips: [{ti
get_element_schema — Return the JSON schema for an element type's type_data. Call this before add_elements / update_elements when you don't already know the field shape for the elem
add_elements — Add many elements across one or more clips in a single tool call. Replaces the per-element tool — always batch. Use after calling get_element_schema to confirm
update_elements — Update many elements across one or more clips in a single tool call. Replaces the per-element tool — always batch. Each item can update position/timing fields
remove_elements — Remove many elements across one or more clips in a single tool call. One entry per element ({clip_index, element_id}). Concurrency: parallel-safe (conflict dom
reorder_elements — Reorder the elements inside a clip. List position IS the z-index — later entries in `ordered_element_ids` render on top of earlier ones. You MUST pass the full
remove_from_project — Remove an element or audio track from a project. - target="element": removes an element from a clip (requires clip_index + element_id) - target="audio": removes
add_audio — Add a music track or sound effect that plays as a project-level audio layer over all clips. Does NOT consume a clip slot. Use this for background music or a sou
update_audio — Mutate fields on an existing music/SFX track by id. Read existing tracks via `get_project.audio_tracks` — the per-entry shape there matches this tool's args, so
voiceover_batch — Set voiceover text and/or trigger speech generation for any number of clips in a single call. Each entry chooses its own action: - "set_text" — set tr
auto_sync — Run the agentic auto-sync pipeline against a clip with a source recording. Detects natural sync points (UI state changes, narrated steps) and inserts sync-marke

+ 1 more — full list in the record JSON.

Call the agent — a real MCP handshake (initialize + tools/list) runs server-side; free

Fetch the full jishie record

curl https://jishie.com/v1/agents/aix_b7f1b1cf94 # full record + verification history · 402 → 0.001 USDC

Run it here — free preview loads instantly; the full record is 0.001 USDC via x402

Verification — what we actually checked

Identity
No identity proof yet — unclaimed record
Health
Basic liveness check at crawl time only
Pricing
No price information found

Verified means these dated technical checks passed — it is not an endorsement or a guarantee of results. Methodology

Provenance

Sources
pulsemcp
Last crawl
2026-08-13
Opt-out
/remove · executed ≤72h

Operate this agent?

Claim it (free) to edit the record and jump the probe queue. Ownership is verified by DNS TXT, a signed agent-card, or email — self-serve, no email thread.

Fast-track · 19 USDC

Embed a live badge

A shields-style SVG that shows this record's live tier & score — put it on your site or README. It updates as the record climbs.

jishie status badge for Clueso

[![jishie](https://jishie.com/v1/agents/aix_b7f1b1cf94/badge.svg)](https://jishie.com/agent.html?id=aix_b7f1b1cf94)
<a href="https://jishie.com/agent.html?id=aix_b7f1b1cf94"><img src="https://jishie.com/v1/agents/aix_b7f1b1cf94/badge.svg" alt="jishie"></a>

On the exchange — sells (standing offers)

No standing offers on the exchange yet. Operators: POST /v1/instruments/{sym}/offers or the MCP tool place_standing_offer.

Declared demand — buys (demand.json)

No declared demand from this operator. Buying too? Publish /.well-known/demand.jsonhow it works.

Similar agents — browser-automation

Raw machine record (what agents receive)
{
  "id": "aix_b7f1b1cf94",
  "name": "Clueso",
  "operator": "(unclaimed - source: pulsemcp · publisher: github.com)",
  "description": "Make videos and docs with your AI agent — describe what you need, every output stays editable.",
  "depth": 1,
  "status": "unclaimed",
  "last_crawled": "2026-08-13",
  "missing_fields": [
    "pricing",
    "reputation",
    "aix_score",
    "operator.identity"
  ],
  "skills": [
    "browser-automation",
    "streaming-tools",
    "text-to-image",
    "text-to-video",
    "voicemail-transcribe"
  ],
  "protocols": {
    "mcp": "https://connect.clueso.io/mcp",
    "a2a": null
  },
  "pricing": null,
  "regions": [
    "global"
  ],
  "languages": [
    "en"
  ],
  "reputation": null,
  "aix_score": null,
  "verification": {
    "identity": "none",
    "health": "liveness-only",
    "pricing": "unknown",
    "last_check": "2026-08-13T21:00:39.674Z"
  },
  "pricing_model": "unknown",
  "links": [
    {
      "label": "homepage",
      "url": "https://www.pulsemcp.com/servers/clueso"
    },
    {
      "label": "repository",
      "url": "https://github.com/clueso-ai/clueso-mcp"
    },
    {
      "label": "listing",
      "url": "https://github.com/desklamp-developers/clueso-mcp"
    }
  ],
  "avatar": "https://github.com/clueso-ai.png?size=160",
  "socials": [
    {
      "label": "github",
      "url": "https://github.com/clueso-ai"
    }
  ],
  "public_stats": {
    "gh_stars": 5
  },
  "payment_method": "oauth",
  "unreachable": false,
  "profile": {
    "mcp_server": "clueso-connect",
    "mcp_version": "1.0.0",
    "tool_count": 40,
    "tools": [
      {
        "name": "get_design_guide",
        "description": "Clueso's house style for composing video clips — how to think about concept, typography, color, composition, motion, and pacing. Read it before composing or editing a clip's visuals when you're NOT following a clueprint, or to fill gaps a clueprint leaves silent. General craft, not clueprint-specific."
      },
      {
        "name": "create_project",
        "description": "Create a new blank Clueso project. Returns the projectId and initialClipId — the project starts with one blank clip already at index 0 (default short duration). Use update_clips to resize/retitle clip 0, add_clips(kind='blank') to append more clips, add_elements to place content, and add_audio to attach a project-level music track.\n\nOptionally place the project inside a folder (use find(type='folders') to find folder IDs) and set a description at creation time."
      },
      {
        "name": "update_project",
        "description": "Update a project's metadata: rename, change description, move to a different folder, or change aspect ratio.\n\nOnly fields you provide are changed. To move a project to the workspace root, pass folder_id=\"\" (empty string).\n\nAspect ratio: pass any \"W:H\" (positive integers). The canvas is fit inside\n1920×1080 keeping the ratio, so element pixel coordinates use the resulting\ncanvas. Common values:\n- \"16:9\" → 1920×1080 (landscape, YouTube/web — default)\n- \"9:16\" → 608×1080  (portrait — TikTok/Reels/Shorts)\n- \"1:1\"  → 1080×1080 (square — Instagram feed)\n- \"3:4\"  → 810×1080  (portrait card)\n- \"4:5\"  → 864×1080  (portrait — Instagram feed)\n\nElement coordinates are stored as fractions of the canvas, so existing elements reflow to the new canvas automatically — no element coordinates are rewritten.\n\nConcurrency: whole-project mutation (conflict domain: the entire project) — serialize. Do not run it in parallel with ANY other mutation on the same project_id, including element/voiceover edits; run them one at a time. (Mutations to different projects run in parallel freely.)"
      },
      {
        "name": "duplicate_project",
        "description": "Clone an entire project — every clip, voiceover, asset, and metadata field — into a fresh project. The copy has its own S3 namespace, so changes or deletions to the source never affect the duplicate.\n\nDefaults:\n- new_title omitted → \"{source title} (copy)\" when no folder_id is given (or it's empty) and the source stays in the same org; pass an explicit non-empty folder_id and the title is preserved.\n- folder_id omitted → copy lives in the same folder as the source.\n\nReturns the new project's id and title. Concurrency: whole-project read+create — serialize against other mutations on the same source_project_id; do not run them in parallel."
      },
      {
        "name": "export_project",
        "description": "Kick off a video export of the project. Returns immediately with an export_id;\nthe export pipeline runs in the background. The user can find the rendered video\nin the Exports tab in the editor.\n\nDefaults: 1080p / 30fps / no captions / English. Pass overrides only when needed."
      },
      {
        "name": "find",
        "description": "Find any resource in Clueso by type, optionally filtered by name or exact id. One tool for listing and searching across the workspace.\n\ntype:\n  • projects | folders | clueprints | workspaces\n  • backgrounds | voices | image_gen_style_packs | element_components\n  • images | videos | music | sfx   — media; each result carries a `source` ('org' = your saved-media library, 'stock' = a stock/curated provider). Scope with `source`, pick the library with `provider` (see below). Stock results are a short described shortlist — pick the best fit and use its `src`. Stock video results also carry `safe_src` and a `video_files` tier list with one entry marked `recommended` — use `safe_src` (or the recommended tier) in add_elements; tiers above 1080p can exceed its ~200MB source cap and fail. For a Freesound music/sfx result, `src` is an OPAQUE handle (not a playable URL) — pass it straight to add_audio and the original is fetched + hosted by Clueso server-side; a `preview_url` is included only so you can tell what it sounds like.\n(image_gen_style_packs = generation style presets for generate_media kind='image' style_id; element_components = saved components (e.g. animations) from THIS WORKSPACE only — there is no community library for components (unlike clueprints); each reports param_keys. Insert one AS-IS with add_elements(component_id=...), or generate a variant from it with base_component_id.)\n\nFilters (all optional):\n  • query     — for stock media it's the search phrase (real semantic search for provider='clueso'; provider keyword search otherwise). For clueprints a query runs a relevance-ranked search across your workspace + the global community library (search_summary, relevance_reason, tags, is_community, fork_count). For everything else it's a case-insensitive name substring.\n  • provider  — which stock library to search (ONE call, no merging). Choose by strength:\n       images  → 'pexels' (default; realistic photography) or 'pixabay' (illustrations, vectors, icons, clip-art — set image_type)\n       videos  → 'pexels' (default; real-world footage) or 'pixabay' (motion graphics — set video_type='animation')\n       music   → 'clueso' (default; our curated, brand-safe library with the best descriptions/search — try this FIRST) or 'freesound' (niche/genre tracks)\n       sfx     → 'freesound' (default; vast sound-effect library) or 'clueso' (curated sfx)\n  • image_type — images + provider='pixabay': 'photo' | 'illustration' | 'vector'\n  • video_type — videos + provider='pixabay': 'film' | 'animation'\n  • id        — exact id; returns just that one record (any type)\n  • source    — media only: 'org' | 'stock' | 'all' (default = org + stock). Under 'all', stock is appended only when a query is given. sfx is stock only.\n  • folder_id — projects + saved media (images/videos/music): restrict to a folder\n  • engine / language     — voices only\n  • creator_id / mine_only — clueprints only\n  • orientation — stock images/videos: 'landscape' | 'portrait' | 'square'\n  • color       — stock images: a color name/hex, e.g. 'blue'\n  • size        — stock videos: 'large' | 'medium' | 'small'\n  • min_duration / max_duration — stock videos + freesound audio: length bounds in seconds\n  • page / limit          — paging for large sets (projects, components, clueprints — a clueprint list is sliced to the limit with no marker when more exist, so page through rather than assuming the first page is everything); stock media ignores these (fixed shortlist)\n\nReturns { type, count, items: [{ id, name, type, ... }] }. Feed the returned id straight into the consuming tool (set_voice, update_clips background, generate_media style_id, add_audio src, use_clueprint, etc.). Any `duration` on a returned item is in SECONDS — pass it straight to add_audio's source_duration."
      },
      {
        "name": "switch_workspace",
        "description": "Switch the active workspace for this session. Use find(type='workspaces') to see available options."
      },
      {
        "name": "get_project",
        "description": "Get a summary of a project: canvas dimensions, per-clip summary (ids, indices, durations, element counts, voiceover_volume), and `audio_tracks` — every music/SFX entry on the project flattened to the same wire shape `add_audio` accepts (id, src, name, source_duration, guide_start_time/end_time, music_start_time/end_time, volume, loop, fade_in, fade_out). To copy an audio track to another project: read it from `audio_tracks` here and feed the same fields to `add_audio` on the target project. Call this to understand what's in the project before making changes."
      },
      {
        "name": "get_clip",
        "description": "Read one clip: its elements (positions/sizes in canvas pixels), voiceover (text, voice, duration, voiceover_volume), background and transition. Pass `render` to also get a PNG of the frame.\n\nASK FOR WHAT YOU NEED. A full read is large — on a dense clip the per-word voiceover array and the element type_data blobs dominate it, and repeated full reads are the main way a long session runs out of context. `select` returns exactly the parts you name:\n\n  select: ['elements.x','elements.y','elements.width','elements.height']   → geometry only, to fix a layout\n  select: ['elements.name','elements.start_time','elements.end_time']      → a timing pass\n  select: ['words']                                                        → word timings only, to sync visuals to narration\n  select: ['elements.textdata','words']                                    → rewrite copy against the VO\n  select: ['elements']                                                     → whole element rows, no words\n  select: []                                                               → no JSON at all (pair with render for the PNG alone — smallest read)\n  (omit select)                                                            → everything; fine for a first look, expensive to repeat\n\n`render` is the other output, and it is separate from `select`: `select` shapes the JSON, `render` produces a PNG.\n\n  render: {}                                → the frame at t=0\n  render: { timestamp: 2.5 }                → the frame 2.5s into the clip\n  render: { save: true }                    → also uploads the PNG and returns presigned_url\n  select: [], render: {}                    → the PNG alone, no JSON\n  select: ['elements'], render: {}           → element rows AND the frame\n\nOmitting `render` renders nothing. `timestamp` and `save` live inside it because they only mean anything for a render — there is no way to ask for them without asking for the image.\n\n`element_ids` is the other axis: it picks WHICH element rows come back, independently of `select`. Combine them for the leanest read — e.g. element_ids: ['el_9'], select: ['elements.x','elements.y'].\n\nElement shape: universal wrapper fields (id, geo, name, x, y, width, height, start_time, end_time, rotation) plus type-specific data (textdata/shapedata/imagedata/videodata/zoomdata) plus an optional `keyframes` array when animated. Keyframes come back in the same flat wire shape add_elements takes — { timestamp, positionX?, positionY?, width?, height?, interpolation? } in canvas pixels — so you can round-trip read → edit → update_elements without reshaping.\n\nClip-level fields include `transition` (the current transition object — sibling of the update_clips `transition` arg; null if none) and `voiceover_words` (per-word timestamps; null on clips with no transcription).\n"
      },
      {
        "name": "update_clips",
        "description": "Update one or more clips in a single call — background, transition, duration, title, visibility, position, and (video clips) the footage transform via video_styling. Always batch.\n\nEach entry: { clip_index, duration?, title?, background_color? | background?, transition?, hidden?, position?, voiceover_volume?, video_styling? }\n- voiceover_volume: 0-100. This is how you make a clip SILENT — set 0 to mute the narration while keeping the text and its word timings. get_clip and get_project report the current value, so read it before muting to restore it later. There is no \"delete voiceover\" tool; muting is the supported way.\n- background_color: hex (keeps the existing pattern/image, changes color). background: full object from find(type='backgrounds') — replaces the whole background.\n- transition: { type, duration, direction?, color? }, or null to remove. Set on the OUTGOING clip — the transition plays at the END of clip_index, into the next clip. Types: circleWipe, rainbowWipe, cornerWipe, stack, colorWipe, dissolve, pushPull, fadeToColor. Directions: rainbowWipe/cornerWipe take topLeft|topRight|bottomLeft|bottomRight; colorWipe left|right; stack/pushPull left|right|top|bottom.\n- position: move the clip to a new zero-based index. Moves apply FIRST (in array order), then property patches — keep reorders in a separate call from property edits so indices stay predictable.\n- video_styling (VIDEO CLIPS ONLY — ignored on slide clips): reposition/resize the clip's recording on the canvas, e.g. into a designed window slot. { position?: [cx, cy], size?: [w, h] } as canvas fractions 0–1 ([1,1] = full-bleed, position is the footage CENTER), plus crop? and border?. Fields merge onto the clip's existing video_styling.\n\nConcurrency: parallel-safe per clip — all property updates land in ONE save. Safe to fan out across DIFFERENT clips. Do NOT run concurrently with element/voiceover edits on the SAME clip, or whole-project mutations (add_clips, add_audio, update_project). When adding audio + resizing clips, run update_clips BEFORE add_audio."
      },
      {
        "name": "remove_clip",
        "description": "Remove a clip from the project by index. Cannot remove the last remaining clip.\n\nAfter removal, all clips with index > clip_index shift down by 1. When removing multiple clips by index, remove from highest index to lowest (or capture clip_ids first and re-derive indices between calls). Concurrency: whole-project mutation (conflict domain: the entire project) — serialize. Do not run it in parallel with ANY other mutation on the same project_id, including element/voiceover edits; run them one at a time. (Mutations to different projects run in parallel freely.)"
      },
      {
        "name": "split_clip",
        "description": "Split a video clip into two clips at the given timestamp. The left clip keeps the original clip ID; the right clip gets a new ID and is inserted immediately after. Elements spanning the split point are duplicated into both clips with adjusted timing. Voiceover transcript and transcribed words are split proportionally.\n\nConcurrency: whole-project mutation (conflict domain: the entire project) — serialize. Do not run it in parallel with ANY other mutation on the same project_id, including element/voiceover edits; run them one at a time. (Mutations to different projects run in parallel freely.)"
      },
      {
        "name": "duplicate_clip",
        "description": "Clone one clip — within the same project, or **from another project** — into a target project.\n\n- Same project: pass `target_project_id` and `source_clip_id` (omit `source_project_id`).\n- Cross-project: pass `target_project_id`, `source_project_id`, `source_clip_id`. The source clip's S3 assets (voiceover audio, original video footage, generated video, etc.) are re-hosted into the target guide's S3 namespace, so the new clip is independent of the source — deleting the source project later won't break it.\n\nInsertion: pass `after_clip_id` to place immediately after a specific clip in the target. Omit to append at end.\n\nReturns the new clip_id and its final index. Concurrency: whole-project mutation (conflict domain: the entire target project) — serialize; do not run in parallel with any other mutation on the same target_project_id."
      },
      {
        "name": "add_clips",
        "description": "Create new clips on a project. One tool, three sources:\n\n- kind=\"blank\": empty slide clips you'll populate with add_elements / generate_media. Pass `clips: [{title?, duration?}, ...]`. Note: create_project already creates clip 0; calling with N blank entries gives N+1 clips total.\n- kind=\"pptx\": extract slides from an uploaded .ppt/.pptx. Pass `mcp_upload_id` (from upload_file), optional `slide_indices`. Each slide becomes a slide_clip with native text/image/shape elements pre-populated.\n- kind=\"video\": cut clips from an uploaded video/audio. Pass `mcp_upload_id` and optionally `mime_type` (default video/mp4), `title`, `voiceover_text`, `cuts: [{start_seconds, end_seconds, heading?}]`, `auto_elements`. Without `cuts` the whole upload becomes one clip. Pipeline (download → transcribe → insert) runs in the background; subsequent calls on the same upload hit a 24h cache and return synchronously. (To overlay a video ONTO an existing clip — picture-in-picture, b-roll, background loop — rather than insert it as its own clip, use add_elements(element_type='video') instead.)\n\nAll variants accept `after_clip_id` to control insertion position; appends at end if omitted.\n\nConcurrency: whole-project mutation (conflict domain: the entire project) — serialize. Do not run it in parallel with ANY other mutation on the same project_id, including element/voiceover edits; run them one at a time. (Mutations to different projects run in parallel freely.)"
      },
      {
        "name": "get_element_schema",
        "description": "Return the JSON schema for an element type's type_data. Call this before add_elements / update_elements when you don't already know the field shape for the element_type you're placing."
      },
      {
        "name": "add_elements",
        "description": "Add many elements across one or more clips in a single tool call. Replaces the per-element tool — always batch.\n\nUse after calling get_element_schema to confirm the type_data shape per element_type. Items within one call are applied in order; returns one result entry per input item so partial success is fine.\n\nReuse instead of rebuilding: an item may pass `component_id` INSTEAD of element_type/type_data to insert a saved component from this workspace exactly as stored — no generation, instant — returning every new element_id plus its parameter_schema; set its content afterwards with update_elements(type_data.parameter_values). Find them with find(type='element_components'). Use `base_component_id` (with element_type='animation' and a prompt) only when you want a NEW variant rather than that component.\n\nGrouping: pass `group: \"<name>\"` to keep a unit together (a card and its label, a stat and its caption) so the user can move or hide it as one thing. Items in one call sharing a name land in the same group, and a later call with that name adds to it. Grouping never changes coordinates. It does affect z-order: a group's members render contiguously at the group's slot, and a NEW group takes the slot of its first member, so grouping already-adjacent elements keeps their z-position while grouping scattered ones pulls them together at the lowest member's slot.\n\nConcurrency: parallel-safe (conflict domain: the individual element). Each element is applied as a granular patch under a per-guide lock and merged onto the latest data, so you can fan this tool out across parallel subagents — even targeting the same clip — as long as they touch different elements. Only two edits to the SAME element id serialize (last write wins). Do NOT run it concurrently with a whole-clip or whole-project mutation on the same guide (update_clips on that clip, add_clips/remove_clip/split_clip/duplicate_clip, add_audio, update_project) — those rewrite a larger scope and would clobber the element.\n\nElement-type quirks (handled per-item):\n  • zoom      → x/y/width/height are ignored; use center_x/center_y in type_data\n  • image     → provide x/y/width/height (the clip is located by clip_index; the clip_id input is accepted but unused)\n  • animation → x/y/width/height default to the full canvas if omitted (the clip is located by clip_index)\nFor everything else, x/y/width/height are required.\n\nAnimation: pass a top-level `keyframes` array (sibling of x/y/type_data, NOT inside type_data) — entries are { timestamp, positionX?, positionY?, width?, height?, interpolation? } in canvas pixels. positionX/Y use the SAME alignment-aware origin as the element's x/y. Text caveat: width/height are not keyframable on text — animate its size with fontSize (letterSpacing/lineHeight/padding* are also keyframable).\n\nPosition origin: x/y is the top-left corner for every element type EXCEPT text — center-aligned text positions by its CENTER POINT, right-aligned text by its top-right. Batching text alongside other shapes? Set alignment explicitly on each text item so you know which origin you're using."
      },
      {
        "name": "update_elements",
        "description": "Update many elements across one or more clips in a single tool call. Replaces the per-element tool — always batch.\n\nEach item can update position/timing fields alone, or type-specific fields via type_data (use get_element_schema in 'update' mode to see what's settable for an element_type). Items within one call are applied in order; partial success is fine.\n\nGrouping: pass `group: \"<name>\"` to move an element into a named group (created on demand), or `group: \"\"` to pull it back out to the clip root. Several items sharing a name collect into one group — the way to tidy loose elements into units the user can move or hide together. Grouping never changes coordinates. It does affect z-order: a group's members render contiguously at the group's slot, and a NEW group takes the slot of its first member, so grouping already-adjacent elements keeps their z-position while grouping scattered ones pulls them together at the lowest member's slot.\n\nCustomising a component: after add_elements(component_id=...), set its content here with type_data.parameter_values on the animation element it returned.\n\nConcurrency: parallel-safe (conflict domain: the individual element), same as add_elements — fan out across subagents freely; the server merges per-element under a per-guide lock. Two edits to the same element id serialize. Do NOT run concurrently with whole-clip/whole-project mutations on the same guide (update_clips on that clip, structural clip ops, add_audio, update_project).\n\nKeyframes: pass a top-level `keyframes` array (sibling of x/y/type_data, NOT inside type_data) to set, or null to clear. Per-entry shape: { timestamp, positionX?, positionY?, width?, height?, interpolation? } in canvas pixels; positionX/Y use the element's alignment-aware origin.\n\nText elements: pass `fit_to_lines: N` to run an automatic widen + font-shrink pass after the entry's regular update lands. The server reshapes the element so the rendered text wraps to at most N lines without overlapping its neighbours."
      },
      {
        "name": "remove_elements",
        "description": "Remove many elements across one or more clips in a single tool call. One entry per element ({clip_index, element_id}).\n\nConcurrency: parallel-safe (conflict domain: the individual element) — same as add_elements/update_elements. Each removal is a granular element_remove patch merged under a per-guide lock, and the whole batch lands in ONE save. Fan out across subagents freely; two edits to the SAME element id serialize. Do NOT run concurrently with whole-clip/whole-project mutations on the same guide (update_clips on that clip, structural clip ops, add_audio, update_project). To remove an audio track (not an element), use remove_from_project(target='audio')."
      },
      {
        "name": "reorder_elements",
        "description": "Reorder the elements inside a clip. List position IS the z-index — later entries in `ordered_element_ids` render on top of earlier ones.\n\nYou MUST pass the full set of element IDs currently in the clip. The handler rejects partial lists so a reorder can never silently drop an element. Get the current list via get_clip."
      },
      {
        "name": "remove_from_project",
        "description": "Remove an element or audio track from a project.\n- target=\"element\": removes an element from a clip (requires clip_index + element_id)\n- target=\"audio\": removes a music/SFX track (requires music_id — returned by add_audio)\n\nConcurrency: target='element' is element-scoped (conflict domain: the individual element) — parallel-safe with other element edits on different elements, same as remove_elements. target='audio' is a whole-project mutation — serialize it against any other mutation on the same project_id. (Mutations to different projects run in parallel freely.)"
      },
      {
        "name": "add_audio",
        "description": "Add a music track or sound effect that plays as a project-level audio layer over all clips. Does NOT consume a clip slot. Use this for background music or a soundtrack that should play under the existing timeline. (For inserting an audio file as its own sequential clip with auto-transcription, use add_clips(kind='video') with an audio mime_type instead.)\n\n- type=\"music\": requires src, name, source_duration. `src` is either the `src` from a find(type='music') result, or an mcp_upload_id (prefixed `mup_`) from upload_file — upload ids resolve to a presigned URL server-side. For a Freesound result the `src` is an opaque handle; pass it through unchanged and Clueso downloads + hosts the original server-side (do not try to fetch it yourself). Get `source_duration` from the find result, or from analyze_audio mode='features' (data.duration_ms / 1000). Optional: guide_start_time, guide_end_time, music_start_time, music_end_time (trim the source track), volume, loop, fade_in, fade_out.\n- type=\"sfx\": pass the `src`, `name`, and `source_duration` from a find(type='sfx') result, plus guide_start_time (Freesound `src` is an opaque handle, resolved server-side just like music). Optional: volume. (Legacy: a fixed-library `sfx_key` like 'whoosh' still works instead of src.)\n\nConcurrency: whole-project mutation (conflict domain: the entire project) — serialize; do not run in parallel with ANY other mutation on the same project_id (including element/voiceover edits). Size your clips BEFORE calling add_audio, or pass guide_end_time explicitly — the default ('end of project') is captured at call time and will NOT auto-extend if clips are added or resized afterwards."
      },
      {
        "name": "update_audio",
        "description": "Mutate fields on an existing music/SFX track by id. Read existing tracks via `get_project.audio_tracks` — the per-entry shape there matches this tool's args, so you can flip a knob (volume, fades, timing, loop) and write it back without losing the track's id.\n\nOnly fields you pass are changed; omitted fields are preserved. To delete a track entirely, use `remove_from_project(target='audio')` instead."
      },
      {
        "name": "voiceover_batch",
        "description": "Set voiceover text and/or trigger speech generation for any number of clips in a single call.\n\nEach entry chooses its own action:\n- \"set_text\"          — set transcript for a clip (clip_index + text)\n- \"generate_speech\"   — async TTS for a clip (clip_id; returns immediately)\n- \"set_and_generate\"  — set text and kick off TTS in one entry (clip_index + clip_id + text)\n\nEntries within one call are applied in order. Returns one result object per input entry. All text-set actions land in ONE save; the TTS for generate/set_and_generate runs async per clip after.\n\nIMPORTANT — generating speech retimes the clip: when audio is generated (generate_speech / set_and_generate), the clip's duration is reset to the spoken audio length (auto-fit). Any element start/end times you authored against a planned duration then play against that new length, and element time past the audio end is cut. So size timings against the spoken length: estimate it up front with estimate_duration (or generate speech BEFORE placing time-sensitive elements), then read the clip back after generation to confirm its final duration.\n\nConcurrency: parallel-safe (conflict domain: a clip's voiceover). The server merges each clip's voiceover under a per-guide lock and preserves that clip's elements, so you can fan voiceover work out across subagents by clip — and it's safe to run alongside element edits. Two edits to the SAME clip's voiceover serialize. Do NOT run concurrently with whole-clip/whole-project mutations on the same guide (update_clips on that clip, structural clip ops, add_audio, update_project)."
      },
      {
        "name": "auto_sync",
        "description": "Run the agentic auto-sync pipeline against a clip with a source recording. Detects natural sync points (UI state changes, narrated steps) and inserts sync-marker nodes into the clip's transcript.\n\nAsync: returns immediately with a status enum from the pre-flight; sync-marker nodes appear in the transcript a few seconds later. Poll get_clip if you need to verify.\n\nCapacity: capped at 3 concurrent runs platform-wide. Returning status='success' means the job was accepted, not that it finished.\n\nSync points are required input for voiceover TTS on video clips — without them, the TTS has no per-step pacing reference. (See resource clueso://docs/sync-points for the full model.)"
      },
      {
        "name": "add_sync_point",
        "description": "Insert one sync marker on a clip's transcript.\n\nUse this when:\n- The user is explicit about WHERE the camera should pause / cut (e.g. \"sync the word 'submit' to 4.2s of the demo\").\n- `auto_sync` ran but missed a step you care about.\n\nHow matching works:\n- `word`: case-insensitive, punctuation-stripped. The first match in the transcript is used unless `occurrence > 1`.\n- `occurrence`: 1-indexed — pass 2 to target the SECOND time that word appears, 3 for the third, etc. Required when the word repeats.\n- `timestamp_seconds`: clip-relative seconds. When the clip has run TTS already (`generated_timestamps` present), the server inverse-maps this to original-recording seconds automatically.\n\nConstraints: the clip MUST be a video clip with a source recording (otherwise the frame thumbnail can't be extracted). The transcript must already contain the word — if not, you'll get `word_not_found` with a 200-char excerpt of the transcript to help you retry."
      }
    ],
    "profiled_at": "2026-08-13T21:00:39.674Z"
  }
}