Doop
Doop: 오픈소스 멀티플레이어 디자인 캔버스 및 AI 에이전트용 MCP
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
full fledged grok bot style mascot animator
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A Blooby mascot is a sphere seen from the front, with features placed on its surface by angle, not by pixel. Drag an eye sideways and it arcs across the face and narrows near the rim, the way it would on a real ball. On top of that rig sit freeform layers (shapes, SVG, text, drawn curves, rubber-hose hands and legs), a clip timeline with keyframes, a dotLottie state machine, and a Copilot that edits through the same functions the UI uses.
The editor, renderer and exporters run entirely in the browser. apps/api adds accounts, cloud projects, a
community preset catalogue, admin tooling and the MCP server. Without it, the editor still runs offline on
the built-in presets.
| Characters | Several mascots per project, each a body, a face layer, eyes and rubber-hose hands and legs that keep their length. Pin a foot or a hand in the world and the limb bends around it. One-click poses, saved mascot templates, roles for any layer |
| Motion | Keyframes on every property with Bézier easing and a value graph, squash & stretch, anchor points, procedural shake / float / pendulum / walk cycle / follow-through / jelly |
| Layers & looks | Shapes, pasted SVG, Google Fonts text on an arc or along a path, pen curves with draw-on trim and taper. Glow, blur, shadow, RGB split, slices, scanlines, flicker, jitter, echo and goo, blend modes, masks, gradients |
| Space | Layer depth with parallax, card turns, a mascot spinning round its sphere, camera pan / zoom / shake, particle bursts that gather into a word |
| States | Each timeline is a state. Rules like "when mood == 2, play Dance" from any state, in a node graph, exported as a real dotLottie state machine |
| Presets | 72 built-ins: moods and idles, text, app screens, the app mascot kit, and cinematic ones (Portal Entrance, Full Body Morph, Glitch Materialize, 3D Card Flip, a 20-second showreel). Every part of one is an ordinary layer, key or effect |
| Copilot | An agent that inspects, searches presets, edits in small batches, previews and verifies. Every run can be reverted or reapplied |
| Admin | Moderation, users, projects, official presets and a splashscreen builder on the editor's own renderer |
| Format | Notes |
|---|---|
| Lottie JSON | Baked per frame from the same scene the canvas draws. Guides are left out. Blend modes, trim offset, flicker, jitter, echo, depth, camera, particles and per-letter motion bake exactly. Glow, blur, shadow, RGB split, slices, scanlines, goo, gradients and masks have no Lottie equivalent here: they are drawn in the editor, GIF, MP4 and PNG, and the export lists which layers lost them |
.lottie | Every state in one composition, with markers and the state machine (s/<id>.json) |
| React Native pack | The .lottie, blooby.machine.json and a generated Mascot.tsx for @lottiefiles/dotlottie-react-native |
| GIF / MP4 / WebM / PNG | Rasterised in the browser. The video container depends on MediaRecorder support |
Twelve of the built-in presets, played straight off the renderer. Under each: the systems it is built from and every property it keys. Open one in the editor and each of these is an ordinary layer, effect, modifier or keyframe track you can select and change. Nothing in the engine is special-cased for them.
Blooby is an MCP server. Copy your link from the editor's AI → Connect AI apps tab, add it to your AI app as a connector, approve on the Blooby page it opens, and ask it to animate. Each edit it makes is saved as it lands and shows up in your open editor. See docs/mcp.
Needs Node.js 22 and pnpm 10 (corepack enable picks up the pinned version).
git clone https://github.com/divyanshu-patil/blooby.git
cd blooby
pnpm install
pnpm dev:web # the editor, offline on the built-in presets: http://localhost:5173
pnpm dev # web (5173), admin (5174) and api (3000) through Turborepo
pnpm run ci # lint + typecheck + test: run it before a pull request
The backend needs a Supabase project, a Postgres connection string and an S3 bucket.
Each app has its own .env.example. Copy it to .env in the same folder. Frontend
values are shipped in the browser bundle, so put only publishable keys in them.
| App | Variable | Required | Purpose |
|---|---|---|---|
apps/web, apps/admin | VITE_SUPABASE_URL | no* | Supabase project URL |
VITE_SUPABASE_PUBLISHABLE_KEY | no* | Supabase publishable key | |
VITE_API_URL | no* | Where apps/api runs, e.g. http://localhost:3000 | |
apps/api | NODE_ENV, PORT | yes | Runtime mode and port (default 3000) |
APP_URL, ADMIN_URL | yes | Web and admin origins (CORS) | |
SUPABASE_URL, SUPABASE_SECRET_KEY, SUPABASE_JWKS_URL | yes | Auth and data. The secret key is server-only | |
DATABASE_URL | yes | Prisma connection. Use the Supabase transaction pooler (:6543, pgbouncer=true&connection_limit=10). Not connection_limit=1 — that serialises every query in the process onto one connection | |
AWS_REGION, AWS_S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | yes | Project JSON storage (private bucket) | |
S3_CORS_ORIGINS | no | Extra origins for the bucket's CORS rules, beyond APP_URL / ADMIN_URL. The bucket needs them — see below | |
ALLOWED_MEDIA_TYPES, MAX_PROJECT_BYTES | yes | Upload limits | |
REDIS_URL | no | Shared rate limits, cache invalidation and MCP state across instances. Unset is fine on one instance — see docker compose up -d redis. Needed when a host runs several (Vercel) | |
OLLAMA_URL | no | Upstream for cloud Copilot requests |
* Leave all three unset in apps/web to run the editor offline on the bundled presets.
apps/api validates its environment at boot (src/config/env.ts) and exits with a
specific message when a value is missing. Copilot API keys are not environment
variables; they are managed from the admin dashboard.
The browser reads a project's JSON straight from S3 through a presigned link, instead of having it proxied through the API — that is what takes two round trips and a median 320KB off every card on the dashboard. A bucket refuses a cross-origin read by default, so on a fresh bucket every project fails to open with "No 'Access-Control-Allow-Origin' header is present" until you run:
pnpm --filter @blooby/api s3:cors # show what is there, and what would change
pnpm --filter @blooby/api s3:cors --apply # write it
It allows GET and HEAD from APP_URL and ADMIN_URL, plus anything in
S3_CORS_ORIGINS. CORS is not the access control here — the presigned signature is, and it
names one object and expires within the hour — so listing an origin grants nothing to
anyone who does not already hold a link the API handed them.
(The app survives a bucket without them: it notices, warns in the console, and reads the document through the API instead. That is several times slower, and it is a fallback, not the arrangement.)
pnpm --filter @blooby/api start. Redis is optional.apps/api/vercel.json installs from the monorepo root and bundles src/serverless.ts into one
file (scripts/bundle.mjs). Set REDIS_URL there: calls land on different instances, and the MCP server's
open project, runs, jobs and approvals are shared through it (docs/mcp/architecture.md).UI (ui/*.tsx) ──► store actions (core/store.ts, one per mutation, undoable)
│
Copilot agent ──► tools (copilot/tools.ts) ─┘ both call core/layers.ts, writeKeyframe, …
▼
Project (core/types.ts) ─► evaluateRig / buildScene (core/scene.ts)
├─► <Shapes> (stage, thumbnails, admin splash, raster)
└─► bakeLottie ─► .lottie / React Native pack
Project in core/types.ts. Old files are upgraded by
numbered, append-only steps in core/migrate.ts.buildScene, so they cannot drift apart.core/props.ts.AGENT_MAP.md traces these flows in more detail.
A project has several timelines, and each one is a state. A timeline holds clips
(Block), keyframe tracks (Track → Keyframe), procedural modifiers (shake, float,
stretch, pendulum), particle emitters and appearance ranges. A track animates one property
path, such as transform.rotation, squish.x, trim.end or limb.b.y. Its keyframes
interpolate with the outgoing key's easing (linear, named presets or a cubic Bézier).
Every animatable property is defined once in core/props.ts; the inspector, timeline,
exporters and Copilot all read that table.
A mascot is a body node plus parts with roles: face, eyeL/eyeR, armL/armR,
legL/legR. Eyes live inside the face, and the face hands the body's sphere down to
them, so eyes still arc when the face moves or turns. Hands ride the face; legs ride the
body. Pin to ground records the foot's world position. After that the leg bends
between the moving hip and the planted foot. Unpin writes the pinned pose back, so
nothing jumps.
Start offset and End offset control which part of a curve's stroke is drawn, as fractions of its length. Keyframe End from 0 to 1 to draw a line on, then Start from 0 to 1 to draw it off. Start greater than End is allowed and draws the same span reversed. The Lottie export writes a trim path.
Squish X/Y multiply onto scale, so a squash stacks on top of any scale animation, and are clamped between 0.4 and 1.8. Set the anchor to the feet ("Squish from: Feet") to keep a squash on the ground. Squish presets are reusable actions, not clips: Apply writes squish keyframes starting at the playhead, which you can edit like any others.
States are timelines. Inputs are Boolean, Numeric, String or Event. The Rules
section and its node graph express "when this input condition holds, play this state":
mood == 1 → Happy
mood == 2 → Dance
mood == 3 → Sad
A rule applies from whatever state is current, so you never wire Idle → Dance, Happy → Dance and Sad → Dance separately. Drag from Any state onto a state in the graph to add a rule. Drag between two states for a transition out of one state only; those are tried first. Blend duration, easing and extra conditions are under More. Because dotLottie has no "any state", export fans each rule out into one edge per other state.
Presets are reusable clips: tracks plus any layers, effects and appearance ranges they
bring. Placing one adds an editable clip on the selected mascot's lane. The library holds
moods and idles, showcase presets (hands, legs, morphs, several mascots), text presets
and app-screen presets, and cinematic presets (core/cinematicPresets.ts) built only from
the editor's own systems: effects, masks, depth, the camera, bursts, walk/follow/jelly and
per-letter keys — open one and every part of it is an ordinary layer, key or effect.
sequence() joins presets end to end, which is how the showreel is made.
The app mascot kit (core/mascotKit.ts) holds shippable states for app screens — Writing,
Generation Failed/Cancelled/Complete, Shuffling Cards, Cards Fan Out, Hello Wave, Happy Idle,
Cloud Float (+ Pro), Refresh Release, No Scripts Yet, Telescope Scan, Disconnected, Clap and Bow,
Published!, Listening, Wrong Code, Thumbs Up, Head Scratch, Mail Sent, Sad Goodbye, Hello Jump In,
Sparkle Excited, Cover Eyes. Each is Lottie-safe and its tagline names the .lottie and state it
belongs to (KIT_ASSETS): make one timeline per state, place the preset, wire the inputs. Users can save
their own, and the community catalogue is served by the API.
A preset layer with a negative zIndex is placed behind the whole rig (portals, scenery);
any other sits on top.
The Copilot runs as an agent loop against Ollama (local, Ollama Cloud or a custom endpoint):
request → inspect project → find functions → search presets → read their keyframes
→ edit in small batches → preview → verify → adjust → finish
find_functions lists the edit tools and every
store action (read from the store's own Editor interface), and call_editor runs one.search_presets matches on what presets actually do
(hand tracks, squash, trim, curved text, several mascots). get_preset returns their
real layers, keyframe times, values and easings.preview scrubs the playhead and reports where layers are drawn.
The agent can also move the playhead, select layers and switch inspector tabs.apps/admin builds the app's splashscreen from the editor's current project, a published
preset or the default mascot. The preview and the live overlay render with the editor's
sceneAt and <Shapes>, so every layer type the editor draws also draws there. A preset
is mounted with the editor's own presetPreviewProject, which brings its layers, effects
and ranges along with its tracks.
| Command | What it does |
|---|---|
pnpm dev | All three apps in watch mode (Turborepo terminal UI) |
pnpm build | Production build of every package |
pnpm test | Vitest across the workspace |
pnpm lint | oxlint across the workspace |
pnpm typecheck | tsc across the workspace |
pnpm run ci | lint + typecheck + test, what the CI workflow runs |
pnpm test:coverage | Tests with a per-file coverage table |
pnpm seed:presets | Seed the API's preset catalogue from apps/api/prisma/presets.seed.json |
pnpm --filter @blooby/studio copilot:test -- <model> "<prompt>" | Run the copilot prompt pipeline against a real Ollama |
packages/studio/ the editor, renderer, exporters and copilot: nearly all the logic
src/core/ data model, store, scene evaluation, layers, mascots, presets, migrations
src/ui/ stage, layers, inspector, timeline, graph editor, state editor, copilot panel
src/export/ Lottie baker, dotLottie container, React Native runtime, raster export
src/copilot/ agent loop, tools, prompt, Ollama client
src/cloud/ API clients, auth, splashscreen overlay
apps/web/ user-facing shell: auth, dashboard, community, cloud editor
apps/admin/ overview, moderation, users, projects, official presets, splashscreens
apps/api/ Express + Prisma + Supabase (routes → controllers → services → repositories)
supabase/ database migrations
brand/ the icon: vector masters, and what every favicon is generated from
packages/studio/src/index.ts is the package's only public surface; the apps import from
@blooby/studio.
brand/ is the source of truth, and brand/blooby-icon.icon/ is the Icon Composer project
everything comes out of. The masters are its own exports — the real icon, with the
gradient, the sheen and the soft shadow:
| Master | Body | For |
|---|---|---|
exports/blooby-icon-2176-dark.png | near-black | light backgrounds |
exports/blooby-icon-1088.png | paper | dark backgrounds |
Those effects are why the icon is a raster and not a vector here: they are a rendering,
not a shape, and an SVG of the same three shapes is a flatter icon wearing the silhouette.
blooby-icon-dark.svg and blooby-icon-light.svg keep that flat geometry for the few
places that need a shape rather than a picture, and geometry.txt records the numbers —
a circle and two pills tilted 21° at two different sizes — so nobody redraws them by eye.
Everything else is generated. pnpm icons rebuilds it after any change to a master:
| Generated | From |
|---|---|
apps/*/public/favicon-{dark,light}-{32,96}.png | both colourways; index.html picks with media |
apps/*/public/favicon.ico (16/32/48) | the dark mark — a bare request has no theme to go on |
apps/*/public/apple-touch-icon.png | dark, on an opaque plate: iOS does not composite transparency |
apps/*/public/icon-{192,512}.png | dark, plated and inset to Android's maskable safe zone |
packages/studio/src/kit/blooby-mark.png | what BloobyMark renders in the app |
apps/api/src/services/brandMark.ts | the same bytes as a data URI, for the share cards |
brand/exports/mark-{dark,light}-256.png | the header of this file |
Both apps carry the same mark, so an installed admin panel is told apart from the app by its name and not by its icon.
pnpm icons
pnpm install.git checkout -b feat/your-change.pnpm run ci, then open a pull request against main.pnpm test # everything
pnpm --filter @blooby/studio test # editor engine, exporters, copilot
pnpm run ci # lint + typecheck + test
Tests sit beside the module they cover (core/rigging.test.ts, copilot/agent.test.ts).
Studio tests run in Node, written as scripts of assertions (core/testkit.ts). The apps
use Testing Library in jsdom. Coverage includes:
To keep the UI and the Copilot in sync:
Project (usually in core/layers.ts).core/store.ts (one commit, so it is undoable),
with a doc comment on the Editor interface. The Copilot's find_functions reads that
comment.project from a component.TOOL_NAMES,
TOOL_DOCS, validate, describe and applyCalls in copilot/tools.ts.PROPS and a case in getProp/setProp.Project? Make the field optional and add a migration step.More guidance: CLAUDE.md (codebase map and conventions), ASSUMPTIONS.md (decisions and known limits), DESIGN.md (visual language), ANIMATION.md (motion craft behind the Copilot).
Read CLAUDE.md for the codebase map and conventions, ASSUMPTIONS.md for decisions and known limits, DESIGN.md for the visual language and ANIMATION.md for the motion craft behind the Copilot.
MIT © 2026 Divyanshu Patil