npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kata-framework/core

v0.5.0

Published

Headless narrative engine for interactive fiction, visual novels, and text adventures. Parse `.kata` scene files into KSON, run them with `KataEngine`, and consume typed frames in any UI framework.

Readme

@kata-framework/core

Headless narrative engine for interactive fiction, visual novels, and text adventures. Parse .kata scene files into KSON, run them with KataEngine, and consume typed frames in any UI framework.

Install

bun add @kata-framework/core

Quick Start

import { parseKata, KataEngine } from "@kata-framework/core";

const scene = parseKata(`
---
id: intro
title: The Beginning
---
:: Narrator :: Welcome, ${player.name}. You have ${player.gold} gold.

:::if{cond="player.gold > 50"}
:: Merchant :: Care to browse my wares?
:::else
:: Merchant :: Come back when you have coin.
:::

* [Buy a sword] -> @shop
* [Leave] -> @town
`);

const engine = new KataEngine({ player: { name: "Hero", gold: 100 } });
engine.registerScene(scene);

engine.on("update", (frame) => {
  console.log(frame.action); // { type: "text", speaker: "Narrator", content: "..." }
});
engine.on("end", ({ sceneId }) => console.log("Scene ended:", sceneId));

engine.start("intro");
engine.next();
engine.makeChoice("c_0");

Features

Engine API

| Method | Description | |--------|-------------| | parseKata(content) | Parse a .kata string → KSONScene | | parseKataWithDiagnostics(content) | Parse with validation warnings/errors | | new KataEngine(ctx, options?) | Create engine with initial context and options (historyDepth) | | engine.registerScene(scene) | Register a parsed scene by scene.meta.id | | engine.start(sceneId) | Start a scene, emit the first frame | | engine.next() | Advance to next action | | engine.makeChoice(choiceId) | Pick a choice; jumps to target scene if present | | engine.back() | Undo last action — restores ctx, scene, and action index | | engine.use(plugin) | Register a plugin with lifecycle hooks | | engine.getSnapshot() / engine.loadSnapshot(raw) | Save/load with Zod-validated migration |

Engine Events

| Event | Payload | When | |-------|---------|------| | "update" | KSONFrame | A new frame is ready to render | | "end" | { sceneId } | Scene has no more actions | | "audio" | AudioCommand | Audio action fired (auto-advances) | | "error" | Diagnostic | Non-fatal error (bad condition, interpolation failure) | | "preload" | string[] | Asset IDs to preload |

.kata Syntax

| Syntax | Description | |--------|-------------| | [bg src="file.mp4"] | Visual directive | | :: Speaker :: text | Dialogue action | | * [Label] -> @scene/id | Choice with optional scene target | | :::if{cond="expr"} ... :::elseif{cond="..."} ... :::else ... ::: | Conditional block with branches | | [wait 2000] | Pause playback (ms) | | [exec] ... [/exec] | Inline code execution | | // comment | Comment line (stripped) | | ${expression} | Variable interpolation | | [tween target="x" property="y" to="1" duration="500"] | Animation tween | | [tween-group parallel] ... [/tween-group] | Grouped tweens (parallel or sequence) |

KSON Action Types

| Type | Shape | |------|-------| | text | { type: "text", speaker, content } | | choice | { type: "choice", choices: [{ id, label, target?, condition?, action? }] } | | visual | { type: "visual", layer, src, effect? } | | condition | { type: "condition", condition, then, elseIf?, else? } | | wait | { type: "wait", duration } | | exec | { type: "exec", code } | | audio | { type: "audio", command: AudioCommand } | | tween | { type: "tween", target, property, from?, to, duration, easing? } | | tween-group | { type: "tween-group", mode: "parallel" \| "sequence", tweens: [...] } |

Plugin System

engine.use({
  name: "my-plugin",
  init(engine) { /* called once on registration */ },
  beforeAction(action, ctx) { console.log(action); return action; },
  afterAction(action, ctx) { /* ... */ },
  onChoice(choice, ctx) { /* ... */ },
  beforeSceneChange(fromId, toId, ctx) { /* ... */ },
  onEnd(sceneId) { /* ... */ },
});

Plugins are validated on registration — invalid objects throw with descriptive errors. See the Plugin Authoring Guide for details.

Official Plugins

All official plugins are tree-shakeable via subpath exports — you only pay for what you import.

import { analyticsPlugin } from "@kata-framework/core/plugins/analytics";
import { profanityPlugin } from "@kata-framework/core/plugins/profanity";
import { autoSavePlugin } from "@kata-framework/core/plugins/auto-save";
import { loggerPlugin } from "@kata-framework/core/plugins/logger";
import { contentWarningsPlugin } from "@kata-framework/core/plugins/content-warnings";
import { validatePlugin } from "@kata-framework/core/plugins/validate";

| Plugin | Description | |--------|-------------| | Analytics | Track scene visits, choice selections, drop-off points, session duration | | Profanity Filter | Censor text/choice labels — configurable word list, replacement strategies, scoping | | Auto-Save | Automatic snapshots on scene changes, choices, every action, or timed intervals | | Debug Logger | Structured lifecycle logging with quiet/normal/verbose levels | | Content Warnings | Tag scenes with warning labels, fire callbacks before entry | | Validate | Runtime plugin validation utility (also used internally by engine.use()) |

Additional Modules

  • Save/Loadengine.getSnapshot() / engine.loadSnapshot(raw) with Zod validation and versioned migration
  • ModdingLayeredVFS for file overlay, mergeScene() for RFC 7396-style scene patching
  • AssetsAssetRegistry for ID→URL mapping, SceneGraph for connectivity analysis and preloading
  • Scene GraphgetOrphans(), getDeadEnds(), toJSON(), toDOT() for story structure analysis

Localization (i18n)

engine.setLocale("ja");
engine.setLocaleFallback("en");
engine.registerLocale("intro", "ja", [
  { index: 0, content: "森へようこそ、${player.name}。" },
  { index: 2, speaker: "商人", content: "おお、裕福な旅人!" },
]);

Locale overrides are resolved before variable interpolation. Locale state is included in snapshots.

Analytics Plugin

import { analyticsPlugin } from "@kata-framework/core/plugins/analytics";

const analytics = analyticsPlugin();
engine.use(analytics);

// After gameplay
const report = analytics.getReport();
// { sceneVisits, choiceSelections, dropOffPoints, averageActionsPerScene, sessionDuration }

analytics.toJSON(); // serializable export
analytics.reset();  // clear all data

Accessibility

Every KSONFrame includes an optional a11y field with hints for screen readers:

  • Text{ role: "dialog", liveRegion: "assertive", label: "Speaker says: ..." }
  • Choice{ role: "group", keyHints: [{ choiceId, hint: "Press N for Label" }] }
  • Visual{ role: "img", description: "Visual: src on layer" }
  • Tween{ description: "target animates property", reducedMotion: true }

All logic evaluation uses new Function with explicit context — never eval().