animarobo 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +93 -2
  3. package/THIRD_PARTY_NOTICES.md +500 -0
  4. package/assets/animarobo_brand.py +143 -0
  5. package/brand-kits/animarobo-demo/brand.json +37 -0
  6. package/brand-kits/animarobo-demo/logo-on-dark.svg +9 -0
  7. package/brand-kits/animarobo-demo/logo.svg +9 -0
  8. package/cli.mjs +9 -0
  9. package/comfy-workflows/i2i-sdxl.json +20 -0
  10. package/comfy-workflows/i2v-wan22-5b.json +156 -0
  11. package/comfy-workflows/music-ace-step.json +21 -0
  12. package/comfy-workflows/t2i-sdxl.json +19 -0
  13. package/comfy-workflows/t2v-wan22-5b.json +24 -0
  14. package/docs/CLIENTS.md +164 -0
  15. package/docs/TOOLS.md +188 -0
  16. package/lib/27YDEYYD.mjs +3 -0
  17. package/lib/2AVPIDPM.mjs +3 -0
  18. package/lib/2YBE2P3S.mjs +9 -0
  19. package/lib/2ZOWLYBY.mjs +73 -0
  20. package/lib/3AOLQIVH.mjs +21 -0
  21. package/lib/43MGJOQW.mjs +3 -0
  22. package/lib/45MJLRTX.mjs +3 -0
  23. package/lib/53WQ5F6R.mjs +7 -0
  24. package/lib/5IWGRKCP.mjs +3 -0
  25. package/lib/6T6H3SGS.mjs +10 -0
  26. package/lib/CVYGKUNG.mjs +3 -0
  27. package/lib/E2H6ESRH.mjs +86 -0
  28. package/lib/E5B5GBVG.mjs +3 -0
  29. package/lib/GEWVQLPB.mjs +3 -0
  30. package/lib/H2VLL64R.mjs +3 -0
  31. package/lib/HWH5POFJ.mjs +3 -0
  32. package/lib/I2XDOZQE.mjs +3 -0
  33. package/lib/J7KLB5DP.mjs +3 -0
  34. package/lib/JX4B2RN3.mjs +3 -0
  35. package/lib/ODDRYKUC.mjs +3 -0
  36. package/lib/ODW7HRU4.mjs +3 -0
  37. package/lib/QWACG3XW.mjs +6 -0
  38. package/lib/QZ5JLUO2.mjs +17 -0
  39. package/lib/RN6NEVUX.mjs +31 -0
  40. package/lib/US7HTISX.mjs +4 -0
  41. package/lib/UVU3FU34.mjs +3 -0
  42. package/lib/UX7IAPLC.mjs +21 -0
  43. package/lib/VAG5VNAN.mjs +3 -0
  44. package/lib/YYR7MRVY.mjs +64 -0
  45. package/lib/ZVOH525Q.mjs +3 -0
  46. package/package.json +26 -4
  47. package/remotion-bundle/155.bundle.js +2 -0
  48. package/remotion-bundle/155.bundle.js.LICENSE.txt +7 -0
  49. package/remotion-bundle/18.bundle.js +2 -0
  50. package/remotion-bundle/18.bundle.js.LICENSE.txt +7 -0
  51. package/remotion-bundle/208.bundle.js +1 -0
  52. package/remotion-bundle/226.bundle.js +1 -0
  53. package/remotion-bundle/25.bundle.js +1 -0
  54. package/remotion-bundle/304.bundle.js +1 -0
  55. package/remotion-bundle/402.bundle.js +1 -0
  56. package/remotion-bundle/410.bundle.js +1 -0
  57. package/remotion-bundle/428.bundle.js +2 -0
  58. package/remotion-bundle/428.bundle.js.LICENSE.txt +7 -0
  59. package/remotion-bundle/471.bundle.js +1 -0
  60. package/remotion-bundle/477.bundle.js +1 -0
  61. package/remotion-bundle/624.bundle.js +1 -0
  62. package/remotion-bundle/63.bundle.js +1 -0
  63. package/remotion-bundle/66.bundle.js +1 -0
  64. package/remotion-bundle/761.bundle.js +1 -0
  65. package/remotion-bundle/843.bundle.js +1 -0
  66. package/remotion-bundle/845.bundle.js +1 -0
  67. package/remotion-bundle/866.bundle.js +1 -0
  68. package/remotion-bundle/872.bundle.js +1 -0
  69. package/remotion-bundle/892.bundle.js +1 -0
  70. package/remotion-bundle/92.bundle.js +1 -0
  71. package/remotion-bundle/982.bundle.js +1 -0
  72. package/remotion-bundle/bundle.js +2 -0
  73. package/remotion-bundle/bundle.js.LICENSE.txt +96 -0
  74. package/remotion-bundle/favicon.ico +0 -0
  75. package/remotion-bundle/index.html +62 -0
  76. package/scripts/setup-voice.sh +66 -0
  77. package/scripts/tts_kokoro_worker.py +35 -0
  78. package/vendor/tex2svg.mjs +22 -0
@@ -0,0 +1,143 @@
1
+ """
2
+ Animarobo brand preamble for Manim scenes. Imported before the agent's scene code.
3
+
4
+ - Brand palette as Manim colours: PRIMARY, SECONDARY, ACCENT, BACKGROUND, TEXT (+ PALETTE dict,
5
+ EXTRA list of further brand colours).
6
+ - Background, default text/maths colour and brand fonts (HEADING_FONT, BODY_FONT) applied.
7
+ - MathTex / Tex work without LaTeX: when no `latex` binary is found (or ANIMAROBO_TEX=mathjax),
8
+ formulas are typeset by Animarobo's bundled MathJax instead.
9
+ """
10
+ import atexit
11
+ import hashlib
12
+ import json
13
+ import os
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ import threading
18
+ from pathlib import Path
19
+
20
+ from manim import * # noqa: F401,F403
21
+ from manim import config, ManimColor, Text, MarkupText, Paragraph, MathTex, Tex, VMobject
22
+ import manim.mobject.text.tex_mobject as _tex_mobject
23
+
24
+ _BRAND = json.loads(os.environ.get("ANIMAROBO_BRAND", "{}"))
25
+ PALETTE = {k: ManimColor(v) for k, v in _BRAND.get("palette", {}).items() if isinstance(v, str)}
26
+ EXTRA = [ManimColor(c) for c in _BRAND.get("palette", {}).get("extra", []) if isinstance(c, str)]
27
+ PRIMARY = PALETTE.get("primary", ManimColor("#58C4DD"))
28
+ SECONDARY = PALETTE.get("secondary", ManimColor("#83C167"))
29
+ ACCENT = PALETTE.get("accent", ManimColor("#FFFF00"))
30
+ BACKGROUND = PALETTE.get("background", ManimColor("#000000"))
31
+ TEXT = PALETTE.get("text", ManimColor("#FFFFFF"))
32
+ HEADING_FONT = _BRAND.get("headingFont") or ""
33
+ BODY_FONT = _BRAND.get("bodyFont") or HEADING_FONT
34
+
35
+ if not _BRAND.get("transparent"):
36
+ config.background_color = BACKGROUND
37
+
38
+ try:
39
+ import manimpango
40
+
41
+ for _f in _BRAND.get("fontFiles", []):
42
+ try:
43
+ manimpango.register_font(_f)
44
+ except Exception:
45
+ pass
46
+ except Exception:
47
+ pass
48
+
49
+ for _cls in (Text, MarkupText, Paragraph):
50
+ _cls.set_default(color=TEXT, **({"font": BODY_FONT} if BODY_FONT else {}))
51
+ for _cls in (MathTex, Tex):
52
+ _cls.set_default(color=TEXT)
53
+ VMobject.set_default(color=TEXT)
54
+
55
+
56
+ # --- MathTex / Tex without LaTeX -----------------------------------------------------------
57
+
58
+ class _MathJax:
59
+ """A persistent `node tex2svg.mjs` worker (one JSON line in, one out)."""
60
+
61
+ def __init__(self):
62
+ self.proc = None
63
+ self.lock = threading.Lock()
64
+
65
+ def svg(self, tex: str) -> str:
66
+ with self.lock:
67
+ if self.proc is None:
68
+ self.proc = subprocess.Popen(
69
+ [os.environ.get("ANIMAROBO_NODE", "node"), os.environ["ANIMAROBO_TEX2SVG"]],
70
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1,
71
+ )
72
+ atexit.register(self.proc.kill)
73
+ self.proc.stdin.write(json.dumps({"tex": tex}) + "\n")
74
+ self.proc.stdin.flush()
75
+ out = json.loads(self.proc.stdout.readline())
76
+ if "error" in out:
77
+ raise ValueError(f"TeX error in {tex!r}: {out['error']}")
78
+ return out["svg"]
79
+
80
+
81
+ _mathjax = _MathJax()
82
+
83
+
84
+ _OPEN = re.compile(r"\\special\{dvisvgm:raw <g id='([^']+)'>\}")
85
+ _CLOSE = re.compile(r"\\special\{dvisvgm:raw </g>\}")
86
+
87
+
88
+ def _to_mathjax(expr: str, text_mode: bool) -> str:
89
+ """
90
+ One MathJax math expression from what Manim would send to LaTeX. MathTex part markers
91
+ (dvisvgm specials) become \\cssId{…}{…}, which MathJax renders as the same <g id=…> groups;
92
+ in text mode (Tex) plain text is wrapped in \\text{} and $…$ stays maths.
93
+ """
94
+ expr = _CLOSE.sub("\x03", _OPEN.sub(lambda m: "\x01" + m.group(1) + "\x02", expr))
95
+ out, buf, math, i = [], "", not text_mode, 0
96
+
97
+ def flush():
98
+ nonlocal buf
99
+ if buf:
100
+ out.append(buf if math else "\\text{" + buf + "}")
101
+ buf = ""
102
+
103
+ while i < len(expr):
104
+ c = expr[i]
105
+ if c == "\x01":
106
+ j = expr.index("\x02", i)
107
+ flush()
108
+ out.append("\\cssId{" + expr[i + 1 : j] + "}{")
109
+ i = j + 1
110
+ continue
111
+ if c == "\x03":
112
+ flush()
113
+ out.append("}")
114
+ elif text_mode and c == "\\" and expr[i + 1 : i + 2] == "$":
115
+ buf += "\\$"
116
+ i += 1
117
+ elif text_mode and c == "$":
118
+ flush()
119
+ math = not math
120
+ else:
121
+ buf += c
122
+ i += 1
123
+ flush()
124
+ return "".join(out) or "\\text{}"
125
+
126
+
127
+ def _mathjax_svg_file(expression, environment=None, tex_template=None):
128
+ text_mode = environment not in (None, "align*", "align", "equation*", "equation", "gather*", "displaymath")
129
+ expr = _to_mathjax(expression.strip(), text_mode)
130
+ if not text_mode and ("&" in expr or "\\\\" in expr):
131
+ expr = "\\begin{aligned}" + expr + "\\end{aligned}"
132
+ out_dir = Path(config.get_dir("tex_dir"))
133
+ out_dir.mkdir(parents=True, exist_ok=True)
134
+ out = out_dir / f"mathjax-{hashlib.sha256(expr.encode()).hexdigest()[:16]}.svg"
135
+ if not out.exists():
136
+ out.write_text(_mathjax.svg(expr), encoding="utf-8")
137
+ return out
138
+
139
+
140
+ TEX_ENGINE = "latex"
141
+ if os.environ.get("ANIMAROBO_TEX") == "mathjax" or not shutil.which("latex"):
142
+ _tex_mobject.tex_to_svg_file = _mathjax_svg_file
143
+ TEX_ENGINE = "mathjax"
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "animarobo-demo",
3
+ "name": "Animarobo Demo",
4
+ "persona": {
5
+ "description": "Animarobo is a friendly, slightly mischievous robot animator who turns ideas into motion. Optimistic, precise, never cynical.",
6
+ "tone": ["playful", "confident", "clear"],
7
+ "audience": "Creators, marketers and developers who want studio-grade motion without a studio",
8
+ "doNot": ["use dark or violent imagery", "use more than two fonts", "use off-palette neon colors"]
9
+ },
10
+ "palette": {
11
+ "primary": "#5B5BF7",
12
+ "secondary": "#22D3EE",
13
+ "accent": "#FFB224",
14
+ "background": "#0E0F1A",
15
+ "text": "#F5F6FF",
16
+ "extra": ["#FF5D8F"]
17
+ },
18
+ "typography": {
19
+ "heading": { "family": "Space Grotesk", "googleFont": "Space Grotesk", "weights": [500, 700] },
20
+ "body": { "family": "Inter", "googleFont": "Inter", "weights": [400, 600, 700] }
21
+ },
22
+ "logo": {
23
+ "file": "logo.svg",
24
+ "fileOnDark": "logo-on-dark.svg",
25
+ "placement": "bottom-right",
26
+ "scale": 0.08
27
+ },
28
+ "visualStyle": {
29
+ "promptSuffix": "clean 3D-illustrated style, soft studio lighting, rounded friendly shapes, indigo and cyan color scheme with warm amber highlights, high detail",
30
+ "negativePrompt": "text, watermark, gore, horror, low quality, blurry, extra limbs, neon green",
31
+ "keywords": ["friendly robot", "rounded", "glossy", "optimistic"],
32
+ "referenceImages": [],
33
+ "cornerRadius": 28,
34
+ "motion": { "energy": "balanced", "defaultTransition": "fade", "transitionSeconds": 0.5 }
35
+ },
36
+ "voice": { "ttsVoice": "af_heart", "speed": 1.0 }
37
+ }
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
2
+ <rect x="60" y="90" width="280" height="230" rx="70" fill="#5B5BF7"/>
3
+ <rect x="190" y="40" width="20" height="55" rx="10" fill="#5B5BF7"/>
4
+ <circle cx="200" cy="38" r="22" fill="#FFB224"/>
5
+ <rect x="100" y="140" width="200" height="110" rx="50" fill="#1B1C2E"/>
6
+ <circle cx="155" cy="195" r="24" fill="#22D3EE"/>
7
+ <circle cx="245" cy="195" r="24" fill="#22D3EE"/>
8
+ <path d="M170 280 Q200 300 230 280" stroke="#F5F6FF" stroke-width="12" fill="none" stroke-linecap="round"/>
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
2
+ <rect x="60" y="90" width="280" height="230" rx="70" fill="#5B5BF7"/>
3
+ <rect x="190" y="40" width="20" height="55" rx="10" fill="#5B5BF7"/>
4
+ <circle cx="200" cy="38" r="22" fill="#FFB224"/>
5
+ <rect x="100" y="140" width="200" height="110" rx="50" fill="#0E0F1A"/>
6
+ <circle cx="155" cy="195" r="24" fill="#22D3EE"/>
7
+ <circle cx="245" cy="195" r="24" fill="#22D3EE"/>
8
+ <path d="M170 280 Q200 300 230 280" stroke="#F5F6FF" stroke-width="12" fill="none" stroke-linecap="round"/>
9
+ </svg>
package/cli.mjs ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; import { fileURLToPath as __fu } from 'node:url'; import { dirname as __dn } from 'node:path';
3
+ const require = __cr(import.meta.url); const __filename = __fu(import.meta.url); const __dirname = __dn(__filename);
4
+ import{C as h,d as y,x as w,y as f}from"./lib/UX7IAPLC.mjs";import{f as p,g}from"./lib/2YBE2P3S.mjs";import"./lib/2AVPIDPM.mjs";import{r as b}from"./lib/QWACG3XW.mjs";import{Q as m,V as l,W as c,Y as u,Z as d}from"./lib/YYR7MRVY.mjs";import"./lib/ZVOH525Q.mjs";import"./lib/US7HTISX.mjs";import"./lib/45MJLRTX.mjs";import"./lib/43MGJOQW.mjs";import"./lib/27YDEYYD.mjs";import"./lib/HWH5POFJ.mjs";import"./lib/ODDRYKUC.mjs";import{parseArgs as S}from"node:util";var v=()=>[{id:"s1",template:"logo-reveal",durationSeconds:3,props:{tagline:"Made to move",style:"scale"}},{id:"s2",template:"title-card",durationSeconds:3.5,props:{eyebrow:"Introducing",title:"Animarobo",subtitle:"Brand-true motion graphics, orchestrated by AI agents"}},{id:"s3",template:"kinetic-text",durationSeconds:3.5,props:{lines:["Open-source engines.","Generative models.","Your brand, every frame."],emphasis:["brand"]},transitionIn:{type:"slide",direction:"from-right",seconds:.5}},{id:"s4",template:"bullet-list",durationSeconds:4,props:{heading:"What it does",bullets:["Remotion motion templates","ComfyUI image & video models","Understands & extends footage"]},transitionIn:{type:"wipe",direction:"from-left",seconds:.5}},{id:"s5",template:"layers",durationSeconds:3,props:{background:{type:"grid",colors:["background","accent"]},layers:[{type:"shape",shape:"circle",x:.42,y:.25,w:.16,h:.284,fill:"accent",animationIn:"pop"},{type:"text",text:"Any layout. Any layer.",x:.1,y:.62,w:.8,h:.15,align:"center",fontSize:.08,startSeconds:.4,animationIn:"slide-up"}]},transitionIn:{type:"iris",direction:"from-right",seconds:.6}},{id:"s6",template:"quote",durationSeconds:3.5,props:{quote:"Like having a motion studio on call.",author:"Every content team, soon"}},{id:"s7",template:"end-card",durationSeconds:3.5,props:{headline:"Start creating",cta:"Connect your agent",url:"github.com/explorian006/animarobo"}}];var s=r=>console.log(typeof r=="string"?r:JSON.stringify(r,null,2)),j=r=>process.stderr.write(`\rrendering ${Math.round(r*100)}% `);async function x(){let{positionals:r,values:e}=S({allowPositionals:!0,options:{http:{type:"boolean"},toolsets:{type:"string"},port:{type:"string",default:"3333"},host:{type:"string",default:"127.0.0.1"},aspect:{type:"string"},brand:{type:"string"},scale:{type:"string"},scene:{type:"string"},at:{type:"string"},project:{type:"string"},name:{type:"string"},text:{type:"string"},font:{type:"string"},weight:{type:"string"},out:{type:"string"},open:{type:"boolean"},ffmpeg:{type:"boolean"},voice:{type:"string"},"no-browser":{type:"boolean"},manim:{type:"boolean"},version:{type:"boolean",short:"v"},help:{type:"boolean",short:"h"}}}),[k,i]=r;if(e.version){let{readFileSync:o}=await import("node:fs"),{REPO_ROOT:t}=await import("./lib/UVU3FU34.mjs");return s(JSON.parse(o(`${t}/package.json`,"utf8")).version)}switch(k){case"mcp":{e.toolsets&&(process.env.ANIMAROBO_TOOLSETS=e.toolsets),(await import("./lib/5IWGRKCP.mjs")).resolveToolsets(process.env.ANIMAROBO_TOOLSETS);let{startHttp:o,startStdio:t}=await import("./lib/E2H6ESRH.mjs");e.http?await o(Number(e.port),e.host):await t();return}case"demo":{let o=await d({name:"demo",brandKitId:e.brand??"animarobo-demo",aspect:e.aspect??"16:9"}),t=await u(o.id,I=>{I.timeline.scenes=v()}),n=await p(t,{onProgress:j,scale:e.scale?Number(e.scale):1});process.stderr.write(`
5
+ `),s({projectId:t.id,...n});break}case"render":{let o=await p(await c(a(i,"projectId")),{onProgress:j,scale:e.scale?Number(e.scale):1});process.stderr.write(`
6
+ `),s(o);break}case"still":s(await g(await c(a(i,"projectId")),{sceneId:e.scene,seconds:e.at?Number(e.at):void 0,scale:e.scale?Number(e.scale):.5}));break;case"analyze":{let o=e.project??(await d({name:"analysis"})).id,t=await w(o,a(i,"video"));s({projectId:o,sourceAssetId:t.asset.id,lastFrameAssetId:t.lastFrameAsset.id,context:t.context});break}case"continue":s(await f({source:a(i,"video"),name:e.name??"continuation",brandKitId:e.brand}));break;case"check":s(y(await c(a(i,"projectId"))));break;case"wordmark":{let o=await h(a(i,"brandKitId"),{text:a(e.text,"--text"),family:e.font,weight:e.weight?Number(e.weight):void 0});return s(o)}case"deliver":{let{deliver:o}=await import("./lib/E5B5GBVG.mjs"),t=await o(r.slice(1),{name:e.name,dest:e.out?(await import("node:path")).resolve(e.out):void 0,open:e.open});return s({dir:t.dir,gallery:t.index,zip:t.zip,counts:t.counts})}case"setup":{let{setup:o}=await import("./lib/53WQ5F6R.mjs"),t=await o({ffmpeg:e.ffmpeg,browser:!e["no-browser"],voice:e.voice,manim:e.manim});s(t),process.exitCode=t.ok?0:1;return}case"doctor":{let{doctor:o}=await import("./lib/53WQ5F6R.mjs"),t=await o();for(let n of t.checks)process.stderr.write(`${n.status==="ok"?"\u2713":n.status==="warn"?"!":"\u2717"} ${n.name.padEnd(20)} ${n.detail}${n.fix?`
7
+ \u2192 ${n.fix}`:""}
8
+ `);s({ok:t.ok,home:t.home}),process.exitCode=t.ok?0:1;return}case"projects":s(await l());break;case"brands":s((await m()).map(({kit:o,dir:t,builtin:n})=>({id:o.id,name:o.name,builtin:n,dir:t})));break;case"providers":s(b());break;default:s(["animarobo \u2013 brand-aware motion graphics & video engine (MCP server)",""," mcp [--http --port 3333] [--toolsets core,sound] start MCP server (stdio by default)"," demo [--aspect 9:16] render the demo timeline"," render <projectId> render a project to MP4"," still <projectId> [--scene id | --at sec]"," analyze <video> [--project id]"," continue <video> --name <n> start a project that extends a video"," check <projectId> brand compliance report"," projects | brands | providers"].join(`
9
+ `))}process.exit(0)}function a(r,e){if(!r)throw new Error(`Missing <${e}> argument`);return r}x().catch(r=>{console.error(r instanceof Error?r.message:r),process.exit(1)});
@@ -0,0 +1,20 @@
1
+ {
2
+ "_animarobo": {
3
+ "description": "Image-to-image (reference/brand-style conditioning) with an SDXL checkpoint. denoise controls how far it departs from the reference.",
4
+ "placeholders": ["prompt", "negative_prompt", "seed", "reference_image"]
5
+ },
6
+ "4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sd_xl_base_1.0.safetensors" } },
7
+ "10": { "class_type": "LoadImage", "inputs": { "image": "{{reference_image}}" } },
8
+ "11": { "class_type": "VAEEncode", "inputs": { "pixels": ["10", 0], "vae": ["4", 2] } },
9
+ "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{prompt}}", "clip": ["4", 1] } },
10
+ "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{negative_prompt}}", "clip": ["4", 1] } },
11
+ "3": {
12
+ "class_type": "KSampler",
13
+ "inputs": {
14
+ "seed": "{{seed}}", "steps": 28, "cfg": 6, "sampler_name": "dpmpp_2m", "scheduler": "karras", "denoise": 0.6,
15
+ "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["11", 0]
16
+ }
17
+ },
18
+ "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } },
19
+ "9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "animarobo", "images": ["8", 0] } }
20
+ }
@@ -0,0 +1,156 @@
1
+ {
2
+ "_animarobo": {
3
+ "description": "Wan 2.2 TI2V-5B image-to-video: animates {{init_image}} (a still, or the last frame of a previous clip for seamless continuation). Same model files as t2v-wan22-5b.",
4
+ "models": [
5
+ "diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
6
+ "text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
7
+ "vae/wan2.2_vae.safetensors"
8
+ ],
9
+ "placeholders": [
10
+ "prompt",
11
+ "negative_prompt",
12
+ "width",
13
+ "height",
14
+ "frames",
15
+ "fps",
16
+ "seed",
17
+ "init_image"
18
+ ]
19
+ },
20
+ "37": {
21
+ "class_type": "UNETLoader",
22
+ "inputs": {
23
+ "unet_name": "wan2.2_ti2v_5B_fp16.safetensors",
24
+ "weight_dtype": "default"
25
+ }
26
+ },
27
+ "38": {
28
+ "class_type": "CLIPLoader",
29
+ "inputs": {
30
+ "clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
31
+ "type": "wan",
32
+ "device": "default"
33
+ }
34
+ },
35
+ "39": {
36
+ "class_type": "VAELoader",
37
+ "inputs": {
38
+ "vae_name": "wan2.2_vae.safetensors"
39
+ }
40
+ },
41
+ "48": {
42
+ "class_type": "ModelSamplingSD3",
43
+ "inputs": {
44
+ "shift": 8,
45
+ "model": [
46
+ "37",
47
+ 0
48
+ ]
49
+ }
50
+ },
51
+ "6": {
52
+ "class_type": "CLIPTextEncode",
53
+ "inputs": {
54
+ "text": "{{prompt}}",
55
+ "clip": [
56
+ "38",
57
+ 0
58
+ ]
59
+ }
60
+ },
61
+ "7": {
62
+ "class_type": "CLIPTextEncode",
63
+ "inputs": {
64
+ "text": "{{negative_prompt}}",
65
+ "clip": [
66
+ "38",
67
+ 0
68
+ ]
69
+ }
70
+ },
71
+ "55": {
72
+ "class_type": "Wan22ImageToVideoLatent",
73
+ "inputs": {
74
+ "width": "{{width}}",
75
+ "height": "{{height}}",
76
+ "length": "{{frames}}",
77
+ "batch_size": 1,
78
+ "vae": [
79
+ "39",
80
+ 0
81
+ ],
82
+ "start_image": [
83
+ "56",
84
+ 0
85
+ ]
86
+ }
87
+ },
88
+ "3": {
89
+ "class_type": "KSampler",
90
+ "inputs": {
91
+ "seed": "{{seed}}",
92
+ "steps": 30,
93
+ "cfg": 5,
94
+ "sampler_name": "uni_pc",
95
+ "scheduler": "simple",
96
+ "denoise": 1,
97
+ "model": [
98
+ "48",
99
+ 0
100
+ ],
101
+ "positive": [
102
+ "6",
103
+ 0
104
+ ],
105
+ "negative": [
106
+ "7",
107
+ 0
108
+ ],
109
+ "latent_image": [
110
+ "55",
111
+ 0
112
+ ]
113
+ }
114
+ },
115
+ "8": {
116
+ "class_type": "VAEDecode",
117
+ "inputs": {
118
+ "samples": [
119
+ "3",
120
+ 0
121
+ ],
122
+ "vae": [
123
+ "39",
124
+ 0
125
+ ]
126
+ }
127
+ },
128
+ "57": {
129
+ "class_type": "CreateVideo",
130
+ "inputs": {
131
+ "images": [
132
+ "8",
133
+ 0
134
+ ],
135
+ "fps": "{{fps}}"
136
+ }
137
+ },
138
+ "58": {
139
+ "class_type": "SaveVideo",
140
+ "inputs": {
141
+ "video": [
142
+ "57",
143
+ 0
144
+ ],
145
+ "filename_prefix": "animarobo/video",
146
+ "format": "auto",
147
+ "codec": "auto"
148
+ }
149
+ },
150
+ "56": {
151
+ "class_type": "LoadImage",
152
+ "inputs": {
153
+ "image": "{{init_image}}"
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "_animarobo": {
3
+ "description": "ACE-Step v1 3.5B text-to-music (Apache-2.0). Mirrors ComfyUI's ACE-Step template; tags = style prompt, lyrics may be '[instrumental]'. Re-export from the official template if node names changed.",
4
+ "models": ["checkpoints/ace_step_v1_3.5b.safetensors"],
5
+ "placeholders": ["prompt", "duration", "seed"]
6
+ },
7
+ "40": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "ace_step_v1_3.5b.safetensors" } },
8
+ "14": { "class_type": "TextEncodeAceStepAudio", "inputs": { "tags": "{{prompt}}", "lyrics": "[instrumental]", "lyrics_strength": 0.99, "clip": ["40", 1] } },
9
+ "44": { "class_type": "ConditioningZeroOut", "inputs": { "conditioning": ["14", 0] } },
10
+ "17": { "class_type": "EmptyAceStepLatentAudio", "inputs": { "seconds": "{{duration}}", "batch_size": 1 } },
11
+ "49": { "class_type": "ModelSamplingSD3", "inputs": { "shift": 5, "model": ["40", 0] } },
12
+ "52": {
13
+ "class_type": "KSampler",
14
+ "inputs": {
15
+ "seed": "{{seed}}", "steps": 50, "cfg": 5, "sampler_name": "euler", "scheduler": "simple", "denoise": 1,
16
+ "model": ["49", 0], "positive": ["14", 0], "negative": ["44", 0], "latent_image": ["17", 0]
17
+ }
18
+ },
19
+ "18": { "class_type": "VAEDecodeAudio", "inputs": { "samples": ["52", 0], "vae": ["40", 2] } },
20
+ "59": { "class_type": "SaveAudioMP3", "inputs": { "audio": ["18", 0], "filename_prefix": "animarobo/music", "quality": "V0" } }
21
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "_animarobo": {
3
+ "description": "Text-to-image with any SDXL-class checkpoint using only core ComfyUI nodes. Swap ckpt_name for your model. For FLUX.2 klein / Z-Image / Qwen-Image, export their official ComfyUI template via Workflow → Export (API) and put the {{placeholders}} into the prompt/size/seed fields.",
4
+ "placeholders": ["prompt", "negative_prompt", "width", "height", "seed", "count"]
5
+ },
6
+ "4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sd_xl_base_1.0.safetensors" } },
7
+ "5": { "class_type": "EmptyLatentImage", "inputs": { "width": "{{width}}", "height": "{{height}}", "batch_size": "{{count}}" } },
8
+ "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{prompt}}", "clip": ["4", 1] } },
9
+ "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{negative_prompt}}", "clip": ["4", 1] } },
10
+ "3": {
11
+ "class_type": "KSampler",
12
+ "inputs": {
13
+ "seed": "{{seed}}", "steps": 28, "cfg": 6.5, "sampler_name": "dpmpp_2m", "scheduler": "karras", "denoise": 1,
14
+ "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0]
15
+ }
16
+ },
17
+ "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } },
18
+ "9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "animarobo", "images": ["8", 0] } }
19
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "_animarobo": {
3
+ "description": "Wan 2.2 TI2V-5B text-to-video (Apache-2.0, runs on ~8-12GB VRAM with fp8/GGUF). Mirrors ComfyUI's official Wan2.2 5B template using native nodes. If your ComfyUI version names nodes differently, open the official template, Export (API), and re-insert the placeholders.",
4
+ "models": ["diffusion_models/wan2.2_ti2v_5B_fp16.safetensors", "text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors", "vae/wan2.2_vae.safetensors"],
5
+ "placeholders": ["prompt", "negative_prompt", "width", "height", "frames", "fps", "seed"]
6
+ },
7
+ "37": { "class_type": "UNETLoader", "inputs": { "unet_name": "wan2.2_ti2v_5B_fp16.safetensors", "weight_dtype": "default" } },
8
+ "38": { "class_type": "CLIPLoader", "inputs": { "clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", "type": "wan", "device": "default" } },
9
+ "39": { "class_type": "VAELoader", "inputs": { "vae_name": "wan2.2_vae.safetensors" } },
10
+ "48": { "class_type": "ModelSamplingSD3", "inputs": { "shift": 8, "model": ["37", 0] } },
11
+ "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{prompt}}", "clip": ["38", 0] } },
12
+ "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "{{negative_prompt}}", "clip": ["38", 0] } },
13
+ "55": { "class_type": "Wan22ImageToVideoLatent", "inputs": { "width": "{{width}}", "height": "{{height}}", "length": "{{frames}}", "batch_size": 1, "vae": ["39", 0] } },
14
+ "3": {
15
+ "class_type": "KSampler",
16
+ "inputs": {
17
+ "seed": "{{seed}}", "steps": 30, "cfg": 5, "sampler_name": "uni_pc", "scheduler": "simple", "denoise": 1,
18
+ "model": ["48", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["55", 0]
19
+ }
20
+ },
21
+ "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["39", 0] } },
22
+ "57": { "class_type": "CreateVideo", "inputs": { "images": ["8", 0], "fps": "{{fps}}" } },
23
+ "58": { "class_type": "SaveVideo", "inputs": { "video": ["57", 0], "filename_prefix": "animarobo/video", "format": "auto", "codec": "auto" } }
24
+ }
@@ -0,0 +1,164 @@
1
+ # Using Animarobo from Claude, Codex, Cursor, VS Code, Windsurf and Gemini CLI
2
+
3
+ Animarobo is one stdio MCP server, `npx -y animarobo mcp`, and the same
4
+ build works in every client below. This page covers how to add it, what each client limits, and
5
+ how to govern which tools an agent may call. The full tool reference, with read-only and
6
+ destructive lists and ready-made approval snippets, is in [TOOLS.md](TOOLS.md).
7
+
8
+ Client facts were checked in October 2026 against each client's docs or source code. Where a
9
+ client doesn't document something, this page says so.
10
+
11
+ ## Before the first use
12
+
13
+ ```bash
14
+ npx -y animarobo setup # FFmpeg, render browser, fonts (~250 MB, once)
15
+ npx -y animarobo doctor # check the install
16
+ ```
17
+
18
+ The first `npx` start downloads the package (about 30 s). Run `setup` once, or give the client a
19
+ generous startup timeout: Codex's documented default is 10 s.
20
+
21
+ ## What the server does for every client
22
+
23
+ | Concern | How Animarobo handles it |
24
+ |---|---|
25
+ | Tool names | Lowercase `[a-z0-9_]`, at most 22 characters, so prefixed names stay short: `mcp__animarobo__…` (Claude Code, Codex), `mcp_animarobo_…` (VS Code, Gemini CLI), server + tool ≤ 60 (Cursor). No hyphens or dots, which Codex rewrites. |
26
+ | Schemas | Plain JSON Schema objects with no top-level unions, `$ref`, nullable unions, `$schema`, `propertyNames` or exclusive bounds. Each schema is under 1.4 kB, below Codex's 5 kB compaction threshold. The server validates the exact schema itself. |
27
+ | Descriptions | Each is ≤ 1,024 characters with the purpose first. Server instructions are ≤ 4,096 characters (Claude Code's cut-off). |
28
+ | Approvals | Every tool carries `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` and `title`, taken from one catalog (`src/mcp/toolCatalog.ts`). |
29
+ | Timeouts | Anything that can take long accepts `async: true`. It returns a job id at once, and you poll `job_status`. Use it for renders in every client. |
30
+ | No MCP roots (Codex; roots are deprecated in the 2026-07-28 spec) | `deliver` writes to `dest`, else the roots, else `ANIMAROBO_OUTPUT_DIR`, else the folder the client started the server in, else `~/.animarobo/deliveries`. |
31
+ | No sampling (Codex, Gemini CLI, Cursor) | `produce_video` uses `ANTHROPIC_API_KEY` or `LLM_API_URL` if set. Otherwise the agent drives the same loop with the step tools: `storyboard_create` → `shot_produce` → `cut_assemble` → `editor_*`. |
32
+ | Images in results | Each image is paired with JSON text that names the file path, so a model without vision still gets the result. Files also come as `resource_link`s, plus a `links` map in the JSON. |
33
+ | Too many tools | `ANIMAROBO_TOOLSETS` (or `mcp --toolsets`) exposes only some groups. See [Toolsets](TOOLS.md#toolsets). |
34
+
35
+ ## Limits by client
36
+
37
+ | Client | Tool limit | Tool-call timeout | Roots | Sampling | Uses annotations |
38
+ |---|---|---|---|---|---|
39
+ | Claude Code | None: tools are deferred and found by tool search | stdio: no per-call timer; calls are backgrounded after 2 min; 30 min idle limit | yes | n/a* | n/a* |
40
+ | Claude Desktop | n/a* | about 60 s for local tools (user reports) | n/a* | n/a* | n/a* |
41
+ | Cursor | Historically 40 across all servers; newer builds load tools lazily (unconfirmed) | about 60 s, not configurable (forum reports) | yes | no | n/a* |
42
+ | Codex (CLI + IDE) | None; tool search for deferred tools | `tool_timeout_sec` (docs: default 60 s) | **no** | **no** | yes: read-only (or non-destructive and closed-world) tools skip approval |
43
+ | VS Code Copilot | 128 tools per request | no default found | yes | yes (with consent) | yes: `readOnlyHint` skips confirmation |
44
+ | Windsurf | 100 across all servers | n/a* | n/a* | n/a* | n/a* |
45
+ | Gemini CLI | None | per-server `timeout`, default 10 min | yes | **no** | `readOnlyHint` for policies and Plan Mode |
46
+
47
+ \*n/a: not documented by the client.
48
+
49
+ With 62 tools, Animarobo fits every limit except Cursor's historic 40. In Cursor, if tools go
50
+ missing, use `ANIMAROBO_TOOLSETS=core` (38 tools), or `marketing` / `video` if no other MCP
51
+ servers are enabled.
52
+
53
+ ## Configuration
54
+
55
+ ### Claude Code
56
+
57
+ ```bash
58
+ claude mcp add animarobo -s user -- npx -y animarobo mcp
59
+ # or the plugin (server + workflow skill):
60
+ # /plugin marketplace add explorian006/animarobo
61
+ # /plugin install animarobo@animarobo
62
+ ```
63
+
64
+ - **Startup:** with a cold `npx` cache, give it more time: `MCP_TIMEOUT=120000 claude`.
65
+ - **Permissions:** run everything except destructive tools without prompts. The `allow`/`ask` snippet is in [TOOLS.md → Approval policies](TOOLS.md#approval-policies).
66
+ - **Env vars:** add them with `-e KEY=value` on `claude mcp add`, or under `env` in `.mcp.json`.
67
+
68
+ ### Codex (CLI and IDE extension)
69
+
70
+ `~/.codex/config.toml`, or a project `.codex/config.toml`:
71
+
72
+ ```toml
73
+ [mcp_servers.animarobo]
74
+ command = "npx"
75
+ args = ["-y", "animarobo", "mcp"]
76
+ startup_timeout_sec = 120 # first npx install
77
+ tool_timeout_sec = 600 # synchronous previews; renders should use async anyway
78
+ # env = { ANIMAROBO_TOOLSETS = "all", ANTHROPIC_API_KEY = "…" }
79
+ # enabled_tools / disabled_tools = [...] to narrow the list
80
+ ```
81
+
82
+ Or run `codex mcp add animarobo -- npx -y animarobo mcp`, then add the timeouts.
83
+
84
+ - **Approvals:** in `auto` mode, read-only tools run without approval. To always ask before destructive tools, see [TOOLS.md](TOOLS.md#approval-policies) for the per-tool `approval_mode = "prompt"` blocks.
85
+ - **Deliveries:** Codex has no roots, so `deliver` writes into the folder Codex started the server in (normally your project). Pass `dest` to choose another place.
86
+
87
+ ### Cursor
88
+
89
+ `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
90
+
91
+ ```json
92
+ { "mcpServers": { "animarobo": {
93
+ "command": "npx", "args": ["-y", "animarobo", "mcp"],
94
+ "env": { "ANIMAROBO_TOOLSETS": "all" }
95
+ } } }
96
+ ```
97
+
98
+ Turn individual tools on or off in Cursor's MCP settings. Ask the agent to use `async: true` for
99
+ renders, because Cursor's tool-call timeout (about 60 s) can't be changed.
100
+
101
+ ### VS Code (GitHub Copilot agent mode)
102
+
103
+ `.vscode/mcp.json`, or `MCP: Open User Configuration`:
104
+
105
+ ```json
106
+ { "servers": { "animarobo": {
107
+ "type": "stdio", "command": "npx", "args": ["-y", "animarobo", "mcp"]
108
+ } } }
109
+ ```
110
+
111
+ Tools can be deselected under **Configure Tools**. `readOnlyHint` tools run without confirmation.
112
+
113
+ ### Claude Desktop
114
+
115
+ `claude_desktop_config.json`: Settings → Developer → Edit Config. It lives in
116
+ `~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows and
117
+ `~/.config/Claude/` on Linux. Restart the app after editing.
118
+
119
+ ```json
120
+ { "mcpServers": { "animarobo": { "command": "npx", "args": ["-y", "animarobo", "mcp"] } } }
121
+ ```
122
+
123
+ Local tools are reported to time out after about 60 s, so ask for `async: true` on renders.
124
+
125
+ ### Windsurf
126
+
127
+ `~/.codeium/windsurf/mcp_config.json`. Newer builds use `~/.config/devin/mcp_config.json`.
128
+
129
+ ```json
130
+ { "mcpServers": { "animarobo": { "command": "npx", "args": ["-y", "animarobo", "mcp"] } } }
131
+ ```
132
+
133
+ Windsurf allows 100 tools across all servers. Hide tools with `"disabledTools": [...]`, or use
134
+ `ANIMAROBO_TOOLSETS` in `env`.
135
+
136
+ ### Gemini CLI
137
+
138
+ `~/.gemini/settings.json` (or project `.gemini/settings.json`):
139
+
140
+ ```json
141
+ { "mcpServers": { "animarobo": {
142
+ "command": "npx", "args": ["-y", "animarobo", "mcp"],
143
+ "timeout": 600000
144
+ } } }
145
+ ```
146
+
147
+ Or run `gemini mcp add animarobo npx -y animarobo mcp`. Narrow the list with
148
+ `includeTools` / `excludeTools`. Don't rename the server to something with `_`, because Gemini's
149
+ policy rules split names on it.
150
+
151
+ ### Cloud agents (Claude Code on the web, Codex cloud)
152
+
153
+ Put the server entry in the repository's `.mcp.json` (Claude Code) or `.codex/config.toml`, and
154
+ run `npx -y animarobo setup` in the environment's setup script. Deliveries
155
+ land in the checkout. Commit them, or download them from the session.
156
+
157
+ ## Governance checklist for teams
158
+
159
+ - **Pin a version:** use `animarobo@<version>` instead of `animarobo` (latest), so updates are deliberate.
160
+ - **Expose only what a team needs:** `ANIMAROBO_TOOLSETS=marketing` for a social team, `core` for template-only work, `all,-director,-editor` to drop autonomous production.
161
+ - **Keep destructive tools behind a prompt:** use the client snippets in [TOOLS.md](TOOLS.md#approval-policies). The destructive tools only touch Animarobo's own projects and brand kits; nothing deletes files outside `~/.animarobo` except `deliver`, which replaces its own previous delivery folder of the same name.
162
+ - **Python execution is opt-in:** `manim_scene` runs agent-written Python, and only if Manim was installed with `setup --manim`. On a shared HTTP server it stays off unless `ANIMAROBO_ALLOW_MANIM=1`.
163
+ - **Secrets are never echoed into tool results.** Provider keys (`ELEVENLABS_API_KEY`, `REPLICATE_API_TOKEN`, …) are read from the server's environment. Set them in the client's `env` block, not in prompts.
164
+ - **Adding a tool:** declare it in `src/mcp/toolCatalog.ts` (group, read/write/destructive, idempotent, network, long-running) and run `npm run tools:doc`. The server refuses to start with an undeclared tool, and the tests check annotations, schema portability and that `TOOLS.md` is current.