@mymosdk/avatar 0.1.1 → 0.1.3

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 (2) hide show
  1. package/README.md +656 -0
  2. package/package.json +3 -2
package/README.md ADDED
@@ -0,0 +1,656 @@
1
+ # mymo-avatar
2
+
3
+ Lightweight animated 3D avatar SDK for any web application.
4
+
5
+ - **23 KB gzip** (core, Three.js not included)
6
+ - **60 FPS** WebGL rendering via Three.js
7
+ - **Framework-agnostic** — Vanilla, React, Vue, Next.js, Nuxt, Svelte, Electron
8
+ - **AI-agnostic** — works standalone or with any LLM/TTS provider
9
+ - **< 5 min** to integrate
10
+
11
+ ## How it works
12
+
13
+ The SDK renders an animated 3D avatar as a fixed floating widget that mounts itself directly to `document.body`. You create one `Avatar` instance and control everything through its chainable API — no framework bindings required unless you want them.
14
+
15
+ Under the hood, six systems work together:
16
+
17
+ | System | Responsibility |
18
+ |---|---|
19
+ | **Renderer** | Three.js WebGL scene with ambient + key + fill lighting, ACES Filmic tone mapping, optional shadows |
20
+ | **Animation Engine** | Procedural bone rotations for gestures + morph target blending for expressions |
21
+ | **Audio Engine** | Web Audio API wrapper — playback, amplitude analysis, gain control |
22
+ | **Lip Sync** | Amplitude-to-viseme mapping; drives mouth morphs in real time |
23
+ | **Asset Loader** | GLB/VRM loading from CDN or any URL; in-memory model cache |
24
+ | **Plugin System** | `avatar.use(plugin)` registers third-party or custom integrations |
25
+
26
+ The avatar widget is a rounded `<div>` containing a `<canvas>`. The widget can be dragged, repositioned, resized, and themed at runtime without recreating it.
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ # npm
34
+ npm install @mymosdk/avatar three
35
+
36
+ # pnpm
37
+ pnpm add @mymosdk/avatar three
38
+
39
+ # yarn
40
+ yarn add @mymosdk/avatar three
41
+ ```
42
+
43
+ > Three.js is a peer dependency. If you already have it in your project, you only need `@mymosdk/avatar`.
44
+
45
+ ---
46
+
47
+ ## Quickstart
48
+
49
+ ```ts
50
+ import { Avatar } from "@mymosdk/avatar"
51
+
52
+ const avatar = new Avatar({
53
+ model: "https://github.com/JairoGerardo/mymo-avatar/releases/download/v0.1.0-assets/Maya.vrm",
54
+ position: "bottom-right",
55
+ size: 200,
56
+ theme: "dark",
57
+ framing: "bust",
58
+ })
59
+ ```
60
+
61
+ The avatar mounts itself to `document.body` as a fixed floating widget immediately.
62
+
63
+ ---
64
+
65
+ ## API
66
+
67
+ ### Constructor options
68
+
69
+ | Option | Type | Default | Description |
70
+ |---|---|---|---|
71
+ | `model` | `string` | — | GLB or VRM URL to load on init |
72
+ | `position` | `AvatarPosition` | `"bottom-right"` | Corner preset |
73
+ | `size` | `number` | `180` | Widget size in px |
74
+ | `theme` | `AvatarTheme` | `"light"` | Background theme |
75
+ | `themeConfig` | `ThemeConfig` | `{}` | Custom background/shadow per theme |
76
+ | `framing` | `AvatarFraming` | `"full"` | Camera framing preset |
77
+ | `framingConfig` | `FramingSliceConfig` | `{}` | Per-mode framing fine-tuning |
78
+ | `draggable` | `boolean` | `false` | Allow drag & drop repositioning |
79
+ | `shadows` | `boolean` | `false` | Enable shadow rendering |
80
+ | `idle` | `boolean` | `true` | Enable idle micro-expression animations |
81
+ | `idleInterval` | `number` | `8000` | Idle animation interval (ms) |
82
+ | `blink` | `boolean` | `true` | Enable auto-blink |
83
+ | `blinkInterval` | `number` | `3500` | Blink interval (ms) |
84
+ | `lipSync` | `boolean` | `true` | Enable audio-driven lip sync |
85
+ | `followMouse` | `boolean` | `false` | Auto-track mouse cursor |
86
+ | `autoHide` | `boolean` | `false` | Hide widget when user scrolls |
87
+ | `zIndex` | `number` | `99999` | CSS z-index of the widget |
88
+
89
+ ---
90
+
91
+ ### Framing
92
+
93
+ Control how much of the model is visible in the widget:
94
+
95
+ ```ts
96
+ avatar.frame("full") // entire body
97
+ avatar.frame("half") // waist up
98
+ avatar.frame("bust") // chest up
99
+ avatar.frame("face") // head only
100
+ ```
101
+
102
+ Fine-tune each mode at runtime:
103
+
104
+ ```ts
105
+ avatar.setFramingConfig({
106
+ bust: { from: 0.70, to: 1.0, lookBias: 0.44 },
107
+ face: { from: 0.82, to: 1.0, lookBias: 0.34 },
108
+ })
109
+ ```
110
+
111
+ | Field | Type | Description |
112
+ |---|---|---|
113
+ | `from` | `number` | Bottom of visible slice (0–1) |
114
+ | `to` | `number` | Top of visible slice (0–1, usually 1.0) |
115
+ | `lookBias` | `number` | Where the camera aims in the slice (0.5 = center; lower = more headroom) |
116
+
117
+ Default slices:
118
+
119
+ | Mode | `from` | `to` | `lookBias` |
120
+ |---|---|---|---|
121
+ | `full` | 0.00 | 1.00 | 0.50 |
122
+ | `half` | 0.48 | 1.00 | 0.50 |
123
+ | `bust` | 0.68 | 1.00 | 0.46 |
124
+ | `face` | 0.80 | 1.00 | 0.36 |
125
+
126
+ ---
127
+
128
+ ### Theme
129
+
130
+ Switch theme at runtime or customize colors and shadows:
131
+
132
+ ```ts
133
+ avatar.setTheme("dark")
134
+
135
+ avatar.setThemeConfig({
136
+ light: {
137
+ background: "radial-gradient(circle at 60% 40%, #ffffff, #e8e8e8)",
138
+ boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
139
+ },
140
+ dark: {
141
+ background: "radial-gradient(circle at 60% 40%, #2a2a2a, #1a1a1a)",
142
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
143
+ },
144
+ })
145
+ ```
146
+
147
+ `ThemeConfig` is `Partial<Record<"light" | "dark", { background?: string; boxShadow?: string }>>`.
148
+
149
+ ---
150
+
151
+ ### Expressions
152
+
153
+ ```ts
154
+ avatar.smile()
155
+ avatar.sad()
156
+ avatar.happy()
157
+ avatar.angry()
158
+ avatar.surprised()
159
+ avatar.thinking()
160
+ avatar.confused()
161
+ avatar.sleep()
162
+ avatar.idle() // reset to neutral
163
+
164
+ // With explicit intensity (0–1)
165
+ avatar.expression("smile", 0.8)
166
+ ```
167
+
168
+ ---
169
+
170
+ ### Gestures
171
+
172
+ ```ts
173
+ avatar.wave()
174
+ avatar.nod()
175
+ avatar.yes() // affirmative nods (larger amplitude)
176
+ avatar.no() // deliberate side-to-side shakes
177
+ avatar.shakeHead()
178
+ avatar.clap()
179
+ avatar.jump()
180
+ avatar.dance() // hip sway with counter-rotating spine
181
+ avatar.thumbsUp()
182
+ ```
183
+
184
+ Gestures support **queuing**: if a gesture is triggered while another is active, the current one accelerates to its exit phase and the new one starts immediately after. Only one gesture can be pending at a time.
185
+
186
+ Gestures are **procedural** — they animate bones directly, so no embedded animation clips are needed in the model file.
187
+
188
+ ---
189
+
190
+ ### States (UI feedback)
191
+
192
+ States combine an expression + animation + a pulsing color ring:
193
+
194
+ ```ts
195
+ avatar.loading() // thinking expression + blue ring
196
+ avatar.success() // happy expression + Yes animation + green ring
197
+ avatar.error() // sad expression + No animation + red ring
198
+ avatar.warning() // surprised expression + orange ring
199
+ avatar.typing() // thinking expression, no ring
200
+ avatar.listening() // smile expression + blue ring
201
+ avatar.processing() // thinking expression + blue ring
202
+ avatar.complete() // happy expression + ThumbsUp animation + green ring
203
+
204
+ avatar.setState("listening") // generic setter
205
+ avatar.clearState() // reset expression, animation, and ring
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Speech & Lip Sync
211
+
212
+ ```ts
213
+ // Play audio with automatic lip sync (AudioBuffer, ArrayBuffer, or URL)
214
+ await avatar.talk(audioBuffer)
215
+
216
+ // Manual control
217
+ avatar.startTalking()
218
+ avatar.stopTalking()
219
+ avatar.pauseTalking()
220
+
221
+ // Explicit phoneme/mouth control
222
+ avatar.setViseme("aa") // phoneme-level lip sync
223
+ avatar.setMouth(0.6) // direct mouth openness: 0 = closed, 1 = fully open
224
+ avatar.setVolume(0.8) // amplitude-driven mouth movement
225
+ ```
226
+
227
+ Supported visemes: `"sil"` `"PP"` `"FF"` `"TH"` `"DD"` `"kk"` `"CH"` `"SS"` `"nn"` `"RR"` `"aa"` `"E"` `"ih"` `"oh"` `"ou"`
228
+
229
+ ---
230
+
231
+ ### Look
232
+
233
+ ```ts
234
+ avatar.lookAtMouse() // follow mouse cursor
235
+ avatar.lookAt(400, 300) // look at screen coordinates (px)
236
+ avatar.lookForward() // return to center
237
+ avatar.randomLook() // random periodic look
238
+ ```
239
+
240
+ ---
241
+
242
+ ### Visibility & Layout
243
+
244
+ ```ts
245
+ avatar.show()
246
+ avatar.hide()
247
+ avatar.move(100, 200) // absolute position (px from top-left)
248
+ avatar.position("top-left") // corner preset
249
+ avatar.size(240) // resize widget
250
+ avatar.scale(1.2) // relative scale
251
+ ```
252
+
253
+ ---
254
+
255
+ ### Animations
256
+
257
+ ```ts
258
+ avatar.play("Wave") // play a named clip from the GLB/VRM file
259
+ avatar.stop()
260
+ ```
261
+
262
+ ---
263
+
264
+ ### Events
265
+
266
+ ```ts
267
+ avatar.on("loaded", () => console.log("avatar ready"))
268
+ avatar.on("modelLoaded", (_, { model }) => console.log("model:", model))
269
+ avatar.on("click", () => avatar.wave())
270
+ avatar.on("speechStart", () => console.log("talking…"))
271
+ avatar.on("speechEnd", () => console.log("done"))
272
+ avatar.on("animationStart", (_, data) => console.log(data))
273
+ avatar.on("animationEnd", () => {})
274
+
275
+ avatar.off("click", handler) // remove a specific listener
276
+ ```
277
+
278
+ Available events: `"click"` `"loaded"` `"modelLoaded"` `"animationStart"` `"animationEnd"` `"speechStart"` `"speechEnd"`
279
+
280
+ ---
281
+
282
+ ### Load models
283
+
284
+ ```ts
285
+ // By direct URL — GLB and VRM both supported
286
+ await avatar.load("https://example.com/my-avatar.glb")
287
+ await avatar.load("https://example.com/my-avatar.vrm")
288
+
289
+ // Local file
290
+ await avatar.load("/models/my-avatar.vrm")
291
+ ```
292
+
293
+ ---
294
+
295
+ ### Lifecycle
296
+
297
+ ```ts
298
+ avatar.destroy() // removes widget from DOM, stops all loops
299
+ ```
300
+
301
+ > All methods except `destroy()` are chainable.
302
+
303
+ ---
304
+
305
+ ## Plugins
306
+
307
+ ### TTS integration (manual)
308
+
309
+ The SDK has no built-in TTS provider — call any TTS service from your **backend**, pass the audio to `avatar.talk()`, and lip sync works automatically.
310
+
311
+ Example with ElevenLabs:
312
+
313
+ ```ts
314
+ // Your backend endpoint (Express, Next.js API Route, Edge Function, etc.)
315
+ // POST /api/speak { text: string } → returns audio/mpeg
316
+
317
+ // Frontend
318
+ async function speak(text: string) {
319
+ avatar.setState("loading")
320
+
321
+ const res = await fetch("/api/speak", {
322
+ method: "POST",
323
+ headers: { "Content-Type": "application/json" },
324
+ body: JSON.stringify({ text }),
325
+ })
326
+
327
+ if (!res.ok) {
328
+ avatar.setState("error")
329
+ return
330
+ }
331
+
332
+ const audio = await res.arrayBuffer()
333
+ avatar.clearState()
334
+ await avatar.talk(audio) // lip sync kicks in automatically
335
+ }
336
+ ```
337
+
338
+ Keeping the TTS call on the backend means your API key is never exposed to the browser. The pattern is the same for any provider: OpenAI TTS, Azure Speech, Google Cloud TTS, etc.
339
+
340
+ ### Build your own plugin
341
+
342
+ ```ts
343
+ import type { AvatarPlugin } from "@mymosdk/avatar"
344
+
345
+ const myPlugin: AvatarPlugin = {
346
+ name: "my-plugin",
347
+ install(avatar) {
348
+ avatar.on("click", () => avatar.wave())
349
+ },
350
+ }
351
+
352
+ avatar.use(myPlugin)
353
+ ```
354
+
355
+ The `install` function receives the full `AvatarApi` (all public methods). Plugins are registered once and live for the lifetime of the avatar.
356
+
357
+ ---
358
+
359
+ ## Framework integrations
360
+
361
+ ### Vanilla JS / TypeScript
362
+
363
+ No adapter needed — import and go:
364
+
365
+ ```ts
366
+ import { Avatar } from "@mymosdk/avatar"
367
+
368
+ const avatar = new Avatar({ model: "https://example.com/my-avatar.vrm", framing: "bust", theme: "dark" })
369
+
370
+ document.querySelector("#wave-btn")?.addEventListener("click", () => avatar.wave())
371
+ ```
372
+
373
+ ---
374
+
375
+ ### React
376
+
377
+ ```bash
378
+ npm install @mymosdk/avatar three
379
+ ```
380
+
381
+ **Hook**
382
+
383
+ ```tsx
384
+ import { useAvatar } from "@mymosdk/avatar/react"
385
+
386
+ function MyComponent() {
387
+ const avatarRef = useAvatar({
388
+ model: "https://...",
389
+ position: "bottom-right",
390
+ framing: "bust",
391
+ idle: true,
392
+ blink: true,
393
+ })
394
+
395
+ return <button onClick={() => avatarRef.current?.wave()}>Wave</button>
396
+ }
397
+ ```
398
+
399
+ `useAvatar` creates the avatar on mount and calls `destroy()` on unmount. The returned `RefObject<Avatar>` gives you the full imperative API.
400
+
401
+ **Component**
402
+
403
+ ```tsx
404
+ import { useRef } from "react"
405
+ import { AvatarWidget } from "@mymosdk/avatar/react"
406
+ import type { Avatar } from "@mymosdk/avatar"
407
+
408
+ export function App() {
409
+ const ref = useRef<Avatar | null>(null)
410
+
411
+ return (
412
+ <>
413
+ <AvatarWidget
414
+ ref={ref}
415
+ model="https://..."
416
+ position="bottom-right"
417
+ theme="dark"
418
+ framing="bust"
419
+ idle
420
+ blink
421
+ draggable
422
+ />
423
+ <button onClick={() => ref.current?.smile()}>Smile</button>
424
+ </>
425
+ )
426
+ }
427
+ ```
428
+
429
+ `AvatarWidget` renders no DOM output — it mounts the avatar directly to `document.body`. Control it via the forwarded ref.
430
+
431
+ ---
432
+
433
+ ### Vue 3
434
+
435
+ ```bash
436
+ npm install @mymosdk/avatar three
437
+ ```
438
+
439
+ **Composable**
440
+
441
+ ```vue
442
+ <script setup lang="ts">
443
+ import { useAvatar } from "@mymosdk/avatar/vue"
444
+
445
+ const avatar = useAvatar({
446
+ model: "https://...",
447
+ position: "bottom-right",
448
+ framing: "bust",
449
+ idle: true,
450
+ blink: true,
451
+ })
452
+ // avatar is a ShallowRef<Avatar | null>
453
+ </script>
454
+
455
+ <template>
456
+ <button @click="avatar.value?.wave()">Wave</button>
457
+ </template>
458
+ ```
459
+
460
+ `useAvatar` creates the avatar in `onMounted` and destroys it in `onUnmounted`.
461
+
462
+ **Component**
463
+
464
+ ```vue
465
+ <script setup lang="ts">
466
+ import { ref } from "vue"
467
+ import { AvatarWidget } from "@mymosdk/avatar/vue"
468
+
469
+ const widgetRef = ref()
470
+ const avatar = () => widgetRef.value?.avatar.value
471
+ </script>
472
+
473
+ <template>
474
+ <AvatarWidget
475
+ ref="widgetRef"
476
+ model="https://..."
477
+ position="bottom-right"
478
+ theme="dark"
479
+ framing="bust"
480
+ :idle="true"
481
+ :blink="true"
482
+ :draggable="true"
483
+ />
484
+ <button @click="avatar()?.wave()">Wave</button>
485
+ </template>
486
+ ```
487
+
488
+ Access the avatar instance via `widgetRef.value.avatar.value` (the component exposes a `avatar` ShallowRef).
489
+
490
+ ---
491
+
492
+ ### Next.js
493
+
494
+ ```bash
495
+ npm install @mymosdk/avatar three
496
+ ```
497
+
498
+ The avatar uses WebGL and `document.body`, so it must run **client-side only**. Always use `dynamic` with `ssr: false`.
499
+
500
+ **App Router (recommended)**
501
+
502
+ ```tsx
503
+ // app/components/AvatarDemo.tsx
504
+ "use client"
505
+
506
+ import { useAvatar } from "@mymosdk/avatar/react"
507
+
508
+ export default function AvatarDemo() {
509
+ const avatarRef = useAvatar({
510
+ model: "https://...",
511
+ position: "bottom-right",
512
+ framing: "bust",
513
+ })
514
+
515
+ return <button onClick={() => avatarRef.current?.wave()}>Wave</button>
516
+ }
517
+ ```
518
+
519
+ ```tsx
520
+ // app/page.tsx
521
+ import dynamic from "next/dynamic"
522
+
523
+ const AvatarDemo = dynamic(() => import("./components/AvatarDemo"), { ssr: false })
524
+
525
+ export default function Page() {
526
+ return <AvatarDemo />
527
+ }
528
+ ```
529
+
530
+ Add `transpilePackages` to `next.config.ts`:
531
+
532
+ ```ts
533
+ import type { NextConfig } from "next"
534
+
535
+ const nextConfig: NextConfig = {
536
+ transpilePackages: ["@mymosdk/avatar"],
537
+ }
538
+
539
+ export default nextConfig
540
+ ```
541
+
542
+ **Pages Router**
543
+
544
+ ```tsx
545
+ // pages/index.tsx
546
+ import dynamic from "next/dynamic"
547
+
548
+ const AvatarWidget = dynamic(
549
+ () => import("@mymosdk/avatar/react").then(m => m.AvatarWidget),
550
+ { ssr: false }
551
+ )
552
+
553
+ export default function Home() {
554
+ return (
555
+ <AvatarWidget
556
+ model="https://..."
557
+ position="bottom-right"
558
+ framing="bust"
559
+ theme="dark"
560
+ />
561
+ )
562
+ }
563
+ ```
564
+
565
+ ---
566
+
567
+ ### CDN (UMD)
568
+
569
+ No build step required:
570
+
571
+ ```html
572
+ <script src="https://unpkg.com/three@0.165.0/build/three.min.js"></script>
573
+ <script src="https://unpkg.com/@mymosdk/avatar/dist/mymo-avatar.umd.js"></script>
574
+ <script>
575
+ const { Avatar } = MymoAvatar
576
+ const avatar = new Avatar({ model: "https://example.com/my-avatar.vrm", theme: "dark", framing: "bust" })
577
+ </script>
578
+ ```
579
+
580
+ ---
581
+
582
+ ## Model formats
583
+
584
+ ### VRM — recommended
585
+
586
+ VRM is the first-class format. All features work out of the box on any valid VRM file because the spec enforces standardized bone names and blendshapes. Gestures are procedurally animated — no embedded animation clips required.
587
+
588
+ ```ts
589
+ await avatar.load("https://example.com/my-avatar.vrm")
590
+ ```
591
+
592
+ Good sources: [VRoid Studio](https://vroid.com/en/studio), [VRoid Hub](https://hub.vroid.com), [Booth](https://booth.pm).
593
+
594
+ ### GLB — best-effort
595
+
596
+ GLB files are supported but feature availability depends on what the model includes. A `console.warn` is emitted at load time. For each feature to work, the model must ship the corresponding data:
597
+
598
+ | Feature | Requirement |
599
+ |---|---|
600
+ | Expressions | Morph targets named `Smile`, `Sad`, `Happy`, `Angry`, etc. |
601
+ | Lip sync | Morph targets: `mouthOpen`, `jawOpen`, or `viseme_*` |
602
+ | Blink | Morph targets: `Blink` or `eyesClosed` |
603
+ | Gestures | Animation clips: `Wave`, `Nod`, `Dance`, etc. |
604
+ | Head look | Bone named `Head`, `head`, or `mixamorigHead` |
605
+
606
+ Missing features degrade gracefully and are skipped silently after the initial load warning.
607
+
608
+ ### Built-in models
609
+
610
+ > **Coming in v1.1.** Named models (`maya`, `robot`, `fox`) will be published as separate npm packages (`@mymo/model-maya`, etc.) and resolved from CDN automatically. Use a direct URL in the meantime:
611
+
612
+ ```ts
613
+ // Use any GLB or VRM by URL today
614
+ await avatar.load("https://example.com/my-avatar.vrm")
615
+ await avatar.load("https://example.com/my-avatar.glb")
616
+ await avatar.load("/models/local-avatar.vrm") // local file
617
+ ```
618
+
619
+ Good free VRM sources: [VRoid Hub](https://hub.vroid.com), [Booth](https://booth.pm).
620
+
621
+ ---
622
+
623
+ ## TypeScript
624
+
625
+ All types are exported from `@mymosdk/avatar`:
626
+
627
+ ```ts
628
+ import type {
629
+ AvatarOptions,
630
+ AvatarPosition, // "bottom-right" | "bottom-left" | "top-right" | "top-left"
631
+ AvatarTheme, // "light" | "dark" | "transparent"
632
+ AvatarFraming, // "full" | "half" | "bust" | "face"
633
+ AvatarEvent, // "click" | "loaded" | "modelLoaded" | "animationStart" | "animationEnd" | "speechStart" | "speechEnd"
634
+ AvatarState, // "loading" | "success" | "error" | "warning" | "typing" | "listening" | "processing" | "complete"
635
+ AvatarPlugin,
636
+ AvatarApi,
637
+ Expression, // "smile" | "sad" | "happy" | "angry" | "surprised" | "thinking" | "confused" | "sleep" | "idle"
638
+ Gesture, // "wave" | "nod" | "yes" | "no" | "shakeHead" | "clap" | "jump" | "dance" | "thumbsUp"
639
+ Viseme,
640
+ FramingSliceConfig,
641
+ FramingModeConfig,
642
+ ThemeConfig,
643
+ } from "@mymosdk/avatar"
644
+ ```
645
+
646
+ ---
647
+
648
+ ## Browser support
649
+
650
+ Chrome 90+, Firefox 88+, Safari 15+, Edge 90+
651
+
652
+ ---
653
+
654
+ ## License
655
+
656
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mymosdk/avatar",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Lightweight animated avatar SDK for any web application",
5
5
  "keywords": [
6
6
  "avatar",
@@ -39,10 +39,11 @@
39
39
  "homepage": "https://github.com/JairoGerardo/mymo-avatar#readme",
40
40
  "files": [
41
41
  "dist",
42
- "../../README.md"
42
+ "README.md"
43
43
  ],
44
44
  "scripts": {
45
45
  "build": "vite build && vite build --config vite.umd.config.ts && tsc -p tsconfig.json --emitDeclarationOnly",
46
+ "prepack": "node -e \"require('fs').copyFileSync('../../README.md', 'README.md')\"",
46
47
  "dev": "vite build --watch",
47
48
  "typecheck": "tsc -p tsconfig.json --noEmit",
48
49
  "test": "vitest run",