Skip to content
maskostudioDocs

All pages

  • Overview/docs/use-cases
  • Mobile app/docs/use-cases#mobile
  • Website/docs/use-cases#website
  • Desktop/docs/use-cases#desktop
  • AI agents/docs/use-cases#agents
  • Videos/docs/use-cases#videos
  • Brand/docs/use-cases#brand
  • Start here/docs/tutorials#start-here
  • All tutorials/docs/tutorials
  • Create your first mascot/docs/tutorials/first-mascot
  • Create an animated character/docs/tutorials/first-animation
  • Make a character talk/docs/tutorials/make-it-talk
  • Put your mascot on your website/docs/tutorials/mascot-on-your-website
  • Show a mascot while your AI thinks/docs/tutorials/ai-loading-mascot
  • Give your AI agent a face/docs/tutorials/ai-agent-face
  • Animate the mascot you already have/docs/tutorials/animate-your-mascot
  • How to make Telegram stickers/docs/tutorials/telegram-stickers
  • How to make Discord stickers/docs/tutorials/discord-stickers
  • Quickstart: your first animation/docs/quickstart
  • API keys and workspaces/docs/authentication
  • How mascots work/docs/how-mascots-work
  • Credits and costs/docs/credits
  • From text/docs/create/from-text
  • From an image/docs/create/from-image
  • From a website/docs/create/from-website
  • From a brand book/docs/create/from-brand-book
  • Reference images/docs/manage/references
  • What you can generate/docs/generation
  • Pose-to-animation walkthrough/docs/generate/workflow
  • Images and poses/docs/generate/images
  • Animations/docs/generate/animations
  • Talking animations/docs/generate/talking
  • Logos, scenes and stickers/docs/generate/design-assets
  • Cursor follower/docs/generate/cursor-follower
  • Batch generation/docs/generate/batch
  • Canvases and releases/docs/canvas
  • Build a canvas/docs/canvas/build
  • Canvas templates/docs/canvas/templates
  • Generate canvas assets/docs/canvas/generate-all
  • Export a canvas/docs/canvas/export
  • Smooth mascot playback/docs/integrations/playback
  • Hosting and file formats/docs/generate/cdn
  • File sizes/docs/generate/file-sizes
  • Image and video exports/docs/manage/media-exports
  • Terminal quickstart/docs/terminal
  • Desktop commands/docs/terminal/commands
  • CLI browser login/docs/authentication/cli
  • Swift SDK/docs/sdk/swift
  • TypeScript and Electron SDK/docs/sdk/typescript
  • Jobs and polling/docs/manage/jobs
  • Webhooks/docs/manage/webhooks
  • Manage mascots and assets/docs/manage/collections
  • Connect an AI agent/docs/ai-agents
  • Masko in ChatGPT/docs/ai-tools/chatgpt
  • Masko for Cursor/docs/ai-tools/cursor
  • Masko for Muse/docs/ai-tools/muse
  • Masko skill for coding agents/docs/ai-tools/skills
  • All endpoints/docs/reference
  • Requests and responses/docs/reference/conventions
  • Errors and recovery/docs/reference/errors
  • Changelog/docs/reference/changelog
  • Migrate to mascots and canvases/docs/manage/studio-migration
Get an API key
Documentation

Give your AI agent a face

shop-app — claude
✻ Welcome to Claude Codecwd: ~/shop-app
    >

    Connected to Claude Code

    Waiting for Claude Code

    Your AI agent runs in the background. Your mascot shows what it is doing and tells you when it needs you. Each step of the agent sends an event, and each event moves the mascot to a new pose.

    20 min · 66 credits · Web, Mac, any agent

    1. Make the clips

    Start with one five-second loop per state: Idle, Thinking, Working, Needs approval, Done and Error. Send the six requests in one batch call, 11 credits each.

    {
      "requests": [
        {
          "type": "animation",
          "animation_model": "standard",
          "name": "Needs approval",
          "image_prompt": "waving one hand to get attention",
          "animation_prompt": "waves twice, then waits",
          "duration": 5,
          "loop": true
        }
      ]
    }

    Add the five other states to requests, then wait for the jobs and keep each clip's urls.webm and urls.hevc. Fun extras like Searching, Found it, Running tests and Deploying are one more request each.

    2. Get the events

    From Claude Code or Codex

    A hook is a command your agent runs at each event, with the event as JSON. Point every event at your app:

    {
      "hooks": {
        "UserPromptSubmit": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "curl -s -m 1 -X POST localhost:4545/event --data-binary @- || true"
              }
            ]
          }
        ]
      }
    }

    Add the same entry for PreToolUse, PostToolUse, PermissionRequest, Notification, Stop, StopFailure and SessionEnd. Codex reads the same format from ~/.codex/hooks.json. Your app reads hook_event_name, and tool_name for tools, and picks the state:

    EventToolState
    UserPromptSubmitThinking
    PreToolUseGrep, Glob, ReadSearching
    PreToolUseEdit, WriteWorking
    PreToolUseBash running testsRunning tests
    PreToolUseBash running a deployDeploying
    PermissionRequest, NotificationNeeds approval
    StopDone
    StopFailureError
    SessionEndIdle

    Let a small model catch the moments

    The hook says which tool runs, not what the agent found. For moments like "found the bug" or "the build failed", send the agent's last words to a small OpenAI model and let it pick. Claude Code puts them in tool_response and last_assistant_message. With no hooks at all, like a chatbot or your own agent loop, the same call drives every state.

    const decision = await openai.decisions.create({
      model: 'gpt-6-luna',
      input: lastMessages,
      questions: [
        {
          type: 'choice',
          name: 'state',
          instructions: 'What is the AI agent doing now?',
          choices: [
            { value: 'thinking', description: 'Reads, plans or writes.' },
            { value: 'found_it', description: 'Just found the cause of a bug.' },
            { value: 'needs_approval', description: 'Asks the user for a yes.' },
            { value: 'done', description: 'Finished and reports back.' },
            { value: 'error', description: 'Something failed.' }
          ]
        }
      ]
    });

    It answers in about a third of a second. If it is unsure or slow, keep the current state.

    3. Show the right clip

    const clips = { thinking: THINKING_URL, working: WORKING_URL, done: DONE_URL };
    
    function show(state) {
      mascot.src = clips[state];
      mascot.play();
    }

    To switch with no blink, use two players: smooth mascot playback.

    Next