Add a small animated companion to a website without adding a backend or telemetry service. Peekling runs in the browser, keeps character Packs as data, and gives the host application control through one documented runtime API.
This monorepo contains the dependency-free browser runtime and separate tools for validating Configuration, Plans, and Packs before they reach a page.
Prerequisites:
- Node.js 22.14.0 or newer
- npm 11.16.0
From the repository root:
npm ci
npm run examplesOpen http://127.0.0.1:4174. You should see four local examples, beginning with
a character that follows the pointer. The example server builds the exact
runtime in this checkout and generates a synthetic Pack in memory. It does not
need an external character package or network service.
See the example learning path for what to try on each page.
Install the runtime as an application dependency:
npm install @peekling/runtimeEvery instance needs a character selection. The example below selects a Pack
served by the application and tells Vite to emit the required runtime
stylesheet. Replace the Pack path with a valid character.json whose States
include idle.
import { hatch } from "@peekling/runtime";
import peeklingStyles from "@peekling/runtime/peekling.css?url";
const companion = hatch({
packUrl: "/peeklings/my-character/character.json",
styles: { url: peeklingStyles },
plan: {
baseline: {
channels: ["state"],
state: { state: "idle" },
},
},
});
try {
await companion.ready;
console.log("Peekling is ready");
} catch (error) {
console.error("Peekling could not start", error);
}
window.addEventListener("pagehide", () => companion.destroy(), { once: true });Expected result: ready resolves after the Pack, atlas, and stylesheet have
been validated and loaded. The instance renders the Pack's idle State until
the Plan or a temporary Override selects another supported State.
For the standard website companion, replace the explicit Plan with
preset: "companion". Named presets compile into the same canonical Plan. The
character is draggable and throwable by default, and a character click toggles
its owned content bubble. Product-specific motion remains plain JSON Plan data,
including viewport traversal, host targets, jumps, and custom SVG paths.
For the complete Configuration shape, error behavior, Events, Effects, and content surfaces, read Configure Peekling.
Pack data --------+
+--> validate --> compile Plan --> evaluate --> Effects
Configuration ----+ ^ | |
| | +--> host surfaces
Browser and application Events --> admission-+ +--------> atlas renderer
Artists provide a data-only Pack. Developers provide Configuration, including one immutable baseline Plan. Peekling validates both, admits bounded browser and application Events, evaluates matching Rules, and composes Effects only when their presentation channels are compatible. Effects can update the character renderer or a trusted host-mounted surface.
This separation keeps character data away from executable host code. The execution model defines ordering, channel ownership, temporary Overrides, lifecycle behavior, and cleanup.
| Need | Surface | What it does |
|---|---|---|
| JavaScript or TypeScript application | hatch(configuration) |
Creates one owned runtime instance |
| Browser-global script | Peekling.hatch(configuration) |
Exposes the same hatch contract from the complete browser artifact |
| HTML-first page | <peekling-character> |
Owns one hatch instance for each connected mount |
| Canvas character presentation | hatchCanvas(configuration) |
Uses Canvas 2D with the same Plan, interaction, content, and lifecycle |
| Programmatic validation | @peekling/preflight |
Checks bounded Configuration, Plan, and optional Pack data |
| Vite validation | @peekling/vite |
Runs Preflight at startup, on watched JSON changes, and before builds |
| Command line and Pack authoring | peekling |
Runs Doctor and the Pack creation, compilation, import, and validation tools |
The ESM root has no registration side effects. Call definePeeklingElement()
when an ESM application wants the Web Component. The complete browser artifact
registers the component automatically when the name is free. Neither surface
replaces an unrelated browser global or custom element.
Use exact version pins for external delivery. Replace both integrity placeholders with the values emitted by the release build, and replace the Pack path with a valid manifest hosted by the application:
<script
defer
src="https://cdn.jsdelivr.net/npm/@peekling/runtime@0.1.5/dist/peekling.min.js"
integrity="sha384-<runtime-sri-from-build>"
crossorigin="anonymous"
></script>
<peekling-character
pack-url="/peeklings/my-character/character.json"
styles-url="https://cdn.jsdelivr.net/npm/@peekling/runtime@0.1.5/dist/peekling.css"
styles-integrity="sha256-<stylesheet-sri-from-build>"
></peekling-character>Expected result: the complete browser artifact claims globalThis.Peekling and
registers <peekling-character> when those names are free. The element creates
one hatch-owned instance after the pinned runtime, stylesheet, and selected Pack
pass their loading and integrity checks.
Each mount must provide one of these inputs:
character, for a name in the runtime's pinned registrypackUrl, for a validated manifest URLpack, for inline Native or normalized Pack data
hatch("peek") is shorthand for the pinned peek registry entry. It performs a
network request for the manifest and selected atlas. Self-hosted applications
can use packUrl or inline Pack data instead.
The runtime verifies manifest and atlas integrity before decoding character art.
An atlasUrl may point to a byte-identical mirror. It is not an image
conversion hook. Density-variant Packs also require an explicit density when
an atlas mirror is supplied.
- Packs are data, never code. Pack strings do not enter HTML-parsing sinks.
- The runtime does not send telemetry and owns no network service.
- Strict Content Security Policy support does not require
unsafe-inlineorunsafe-eval. - Closed Shadow DOM contains runtime presentation without pretending to be a security boundary.
- Paused, backgrounded, and dismissed instances stop ordinary animation, evaluation, and collection work.
destroy()releases listeners, observers, timers, roots, and object URLs owned by that instance.- Host mount functions remain trusted application code. Their failures are contained at the surface boundary where the browser permits it.
The detailed browser, CSP, CORS, iframe, top-layer, and host-layout boundaries are documented in Browser compatibility and hosting.
| Package | Install role | Browser runtime graph |
|---|---|---|
@peekling/runtime |
Application dependency | Required when the application uses Peekling |
@peekling/preflight |
Development dependency | Excluded |
@peekling/vite |
Development dependency | Excluded |
@peekling/cli |
Development dependency or command-line tool | Excluded |
@peekling/adapter-codex-pet |
Optional interoperability dependency | Included only when the application imports it |
Character artwork, website code, design-system sources, and framework-specific adapters are outside this repository. Nothing in those products is silently bundled into the runtime.
Doctor checks JSON Configuration and optional Pack data without importing application modules or contacting the network:
npm install --save-dev @peekling/cli @peekling/preflight
npx peekling doctor ./peekling.json --pack ./character.json
npx peekling doctor ./peekling.json --pack ./character.json --jsonVite applications can run the same semantic validation at startup, during watched JSON changes, and before production builds:
npm install --save-dev @peekling/viteRead the @peekling/cli,
@peekling/preflight, and
@peekling/vite guides for complete examples.
The complete browser artifact has separate gzip and Brotli release gates. The 0.1.5 record below measures the interaction and renderer release with the exact release toolchain and required reserve.
The recorded canonical delivery measurement is 38,428 bytes gzip and 33,684 bytes Brotli. Against the 40 KiB gzip and 40 KiB Brotli caps, that recorded build leaves 2,532 bytes of gzip headroom and 7,276 bytes of Brotli headroom. After the required 256-byte reserve, 2,276 gzip bytes and 7,020 Brotli bytes remain for that build.
Node 22.14.0, npm 11.16.0, and Node's default zlib compression define the exact release-size measurement environment. Other supported Node versions can run a local size check, but they do not certify the historical figures.
Run the core repository gate:
npm ci
npm run checknpm run check maps to check:core. It covers formatting, build, lint,
boundaries, workflow safety, package metadata, distribution contents, types,
unit tests, bundle size, and the runtime performance smoke test.
Run browser behavior separately:
npm run test:browserThat Playwright command exercises Chromium, Firefox, and WebKit. The canonical browser performance gate is a separate release workflow described in Browser performance release evidence.
Useful focused commands:
| Command | Purpose |
|---|---|
npm run examples |
Build and serve the local learning examples |
npm run test:examples |
Test example structure and Chromium behavior |
npm run release:verify:packages |
Build clean package archives and test consumer imports |
npm run release:verify:local |
Rehearse local release gates without remote assertions |
npm run size |
Check compressed runtime and ESM measurements |
The release guide owns tagging, provenance, CI, and package publication procedure.
Use the documentation map when you are not sure which guide owns a question.
Start here:
Understand the engine:
- Execution model
- Character interaction and motion
- Engine design
- Schema and type references
- Browser performance release evidence
Work on the repository:
The engine and its original tooling are Apache-2.0. Character artwork keeps its
own Pack license. See licensing and attribution and
NOTICE for the repository boundary.

