Skip to main content

Getting Started

This is the shortest production-oriented path to a working Charivo app. If you just want to see a character talk first, Quick Try gets you there with an API key and no server — but it is not what you ship.

Start with:

@charivo/core
@charivo/llm + @charivo/llm/remote
@charivo/tts + @charivo/tts/remote
@charivo/render + @charivo/render-live2d
server routes backed by @charivo/server/* providers

This is the default browser setup across the repo. It keeps vendor credentials on the server and leaves room to add STT or realtime later without changing the overall shape of the app.

Install

pnpm add \
@charivo/core \
@charivo/llm \
@charivo/tts \
@charivo/render @charivo/render-live2d

For the server side:

pnpm add \
@charivo/server

Quick Try (Dev Only)

To see a character talk before writing any server code, the openai subpaths ship direct browser clients that call OpenAI straight from the page. They take your API key and send it to the browser, so this path is for local experiments only.

Never ship this. Anyone who opens devtools can read the key. Once it works, move to Minimal Browser Setup and Minimal Server Routes below, which keep the key on your server. The rest of the app stays the same — only the client factories change.

import { Charivo } from "@charivo/core";
import { createLLMManager } from "@charivo/llm";
import { createOpenAILLMClient } from "@charivo/llm/openai";
import { createTTSManager } from "@charivo/tts";
import { createOpenAITTSPlayer } from "@charivo/tts/openai";
import { createRenderManager } from "@charivo/render";
import { createLive2DRenderer } from "@charivo/render-live2d";

const OPENAI_API_KEY = import.meta.env.VITE_OPENAI_API_KEY;

const canvas = document.querySelector("canvas")!;

const charivo = new Charivo();

const renderer = createLive2DRenderer({ canvas });
const renderManager = createRenderManager(renderer, { canvas });

await renderManager.initialize();
await renderManager.loadModel?.("/live2d/Hiyori/Hiyori.model3.json");

charivo.attachRenderer(renderManager);
charivo.attachLLM(
createLLMManager(
createOpenAILLMClient({ apiKey: OPENAI_API_KEY, model: "gpt-4.1-nano" }),
),
);
charivo.attachTTS(
createTTSManager(createOpenAITTSPlayer({ apiKey: OPENAI_API_KEY })),
);

charivo.setCharacter({
id: "hiyori",
name: "Hiyori",
personality: "Cheerful and helpful assistant",
voice: { voiceId: "marin" },
});

await charivo.userSay("Hello");

Minimal Browser Setup

import { Charivo, CharivoError } from "@charivo/core";
import { createLLMManager } from "@charivo/llm";
import { createRemoteLLMClient } from "@charivo/llm/remote";
import { createTTSManager } from "@charivo/tts";
import { createRemoteTTSPlayer } from "@charivo/tts/remote";
import { createRenderManager } from "@charivo/render";
import { createLive2DRenderer } from "@charivo/render-live2d";

const canvas = document.querySelector("canvas")!;

const charivo = new Charivo();

const renderer = createLive2DRenderer({ canvas });
const renderManager = createRenderManager(renderer, {
canvas,
mouseTracking: "document",
});

await renderManager.initialize();
await renderManager.loadModel?.("/live2d/Hiyori/Hiyori.model3.json");

charivo.attachRenderer(renderManager);
charivo.attachLLM(
createLLMManager(createRemoteLLMClient({ apiEndpoint: "/api/chat" })),
);
charivo.attachTTS(
createTTSManager(createRemoteTTSPlayer({ apiEndpoint: "/api/tts" })),
);

charivo.setCharacter({
id: "hiyori",
name: "Hiyori",
personality: "Cheerful and helpful assistant",
voice: { voiceId: "marin" },
});

try {
await charivo.userSay("Hello");
} catch (error) {
if (error instanceof CharivoError) {
console.error(error.code, error.message);
}
throw error;
}

await charivo.dispose();

Minimal Server Routes

Browser clients should call your own routes, not vendor APIs directly. The routes below are Next.js route handlers matching what /api/chat and /api/tts above expect.

LLM route (/api/chat):

import { NextRequest, NextResponse } from "next/server";
import { createOpenAILLMProvider } from "@charivo/server/openai";

export async function POST(request: NextRequest) {
const { messages } = await request.json();

const provider = createOpenAILLMProvider({
apiKey: process.env.OPENAI_API_KEY!,
model: "gpt-4.1-nano",
});

try {
const message = await provider.generateResponse(messages);
return NextResponse.json({ success: true, message });
} catch (error) {
console.error("LLM Provider Error:", error);
return NextResponse.json(
{ error: "Failed to generate response" },
{ status: 500 },
);
}
}

TTS route (/api/tts):

import { NextRequest, NextResponse } from "next/server";
import { createOpenAITTSProvider } from "@charivo/server/openai";

export async function POST(request: NextRequest) {
const { text, voice = "marin", speed = 1 } = await request.json();

const provider = createOpenAITTSProvider({
apiKey: process.env.OPENAI_API_KEY!,
defaultVoice: "marin",
defaultModel: "gpt-4o-mini-tts",
});

try {
const audio = await provider.generateSpeech(text, { voice, rate: speed });
return new NextResponse(audio, {
headers: { "Content-Type": "audio/wav" },
});
} catch (error) {
console.error("TTS Provider Error:", error);
return NextResponse.json(
{ error: "Failed to generate speech" },
{ status: 500 },
);
}
}

For a full Next.js example, see Examples Web.

TypeScript Note

If your app imports subpaths such as @charivo/llm/remote, use a TypeScript module resolution mode that supports package exports: "bundler", "node16", or "nodenext".

What You Get

  • typed orchestration through Charivo
  • character-aware LLM history management
  • server-mediated TTS playback
  • Live2D rendering with mouse tracking
  • a clean path to add STT or realtime later

Error Handling

Public Charivo APIs now throw typed errors from @charivo/core. Prefer instanceof CharivoError or error.code checks instead of parsing messages.