taleshaper
v0.1.0
Published
Envelope-driven story shaping toolkit with schemas, providers, and session orchestration.
Maintainers
Readme
TaleShaper
Envelope-driven story shaping for TypeScript.
TaleShaper treats a story as an ordered list of scenes and treats envelopes as numeric control curves aligned to those scenes. When an envelope changes, the affected scenes can be marked for regeneration and rewritten without replacing the whole story.
This package includes:
- Zod schemas and TypeScript types for stories, scenes, characters, and envelopes
TaleShaperSession, a stateful coordinator for generation, envelope evaluation, regeneration, and save/load- streaming preview APIs for story generation, envelope evaluation, and scene regeneration
- provider adapters for OpenAI, vLLM, LM Studio, and a local dry-run provider
Status
TaleShaper is an early prototype. APIs may change before 1.0.
Install
npm install taleshaperRequirements:
- Node.js 20 or newer
- TypeScript projects should use ESM-compatible module resolution
Quick Start
import { DryRunProvider, TaleShaperSession } from "taleshaper";
const session = new TaleShaperSession({
llm: new DryRunProvider()
});
const state = await session.generateStory({
brief: "A conductor protects the last tram line in a sinking floating city.",
chapterCount: 5
});
console.log(state.story?.title);
console.log(state.envelopes.map((envelope) => envelope.info.name));generateStory() also creates default happiness envelopes for the generated
characters. Use syncEnvelopes() to ask the provider to evaluate envelope
values against the current story.
await session.syncEnvelopes();
for (const envelope of session.getEnvelopes()) {
console.log(envelope.info.name, envelope.info.character.name, envelope.values);
}Streaming
Streaming methods return AsyncIterable events. Render preview events for
live feedback and treat final events as the committed result.
for await (const event of session.streamGenerateStory({
brief: "A conductor protects the last tram line in a sinking floating city.",
chapterCount: 5
})) {
if (event.type === "preview" || event.type === "final") {
console.log(event.type, event.state.story?.title);
console.log(event.state.story?.scenes.at(0)?.content);
}
}Envelope evaluation can stream partial value updates too.
for await (const event of session.streamSyncEnvelopes()) {
if (event.type === "preview" || event.type === "final") {
console.log(event.type, event.state.envelopes);
}
}Regeneration
Editing an evaluated envelope value marks the corresponding scene as a regeneration target.
const firstEnvelope = session.getEnvelopes()[0];
if (firstEnvelope?.values) {
session.setEnvelopeValue(firstEnvelope.info.id, 2, 4);
}
console.log(session.getRegenerateTargetSceneIds());Regenerate only the changed scenes:
const state = await session.regenerateChangedScenes();
console.log(state.story?.scenes.map((scene) => scene.title));Use streaming regeneration when you want live scene previews:
for await (const event of session.streamRegenerateChangedScenes()) {
if (event.type === "preview" || event.type === "final") {
console.log(event.type, event.regeneratedScenes);
}
}For faster feedback after envelope edits, pass rapid: true. The default is
false.
await session.regenerateChangedScenes({ rapid: true });Envelopes
Add an envelope:
const character = session.getState().story?.characters[0];
if (!character) {
throw new Error("Generate a story before adding an envelope.");
}
session.addEnvelope({
info: {
id: `env-resolve-${character.id}`,
name: "resolve",
character,
minDescription: `${character.name} gives up.`,
maxDescription: `${character.name} commits fully.`
},
values: null
});
await session.syncEnvelopes({
envelopeIds: [`env-resolve-${character.id}`]
});Remove an envelope:
session.removeEnvelope("env-resolve-char-1");Insert a new chapter after an existing scene:
session.insertNewChapter(1, {
title: "The flooded depot",
content: "Kaito finds the tram depot already half underwater."
});Save And Load
const savedJson = session.saveJson();
const restored = TaleShaperSession.load({
llm: new DryRunProvider(),
saved: savedJson
});
console.log(restored.getState().story?.title);Examples
The repository includes runnable examples under
packages/taleshaper/examples.
The main session example covers story generation, streaming events, envelope evaluation, adding and removing envelopes, inserting a chapter, regenerating changed scenes, and save/load.
bun run example:sessionProviders
Dry-run provider:
import { DryRunProvider } from "taleshaper";
const llm = new DryRunProvider();OpenAI:
import { createOpenAIProvider } from "taleshaper/provider/openai";
const llm = createOpenAIProvider({
apiKey: process.env.OPENAI_API_KEY!,
model: "gpt-5.4-nano"
});LM Studio:
import { LmStudioProvider } from "taleshaper/provider/lmstudio";
const llm = new LmStudioProvider({
baseUrl: "http://127.0.0.1:1234/v1",
model: "gemma-4-e4b-it"
});vLLM:
import { VllmProvider } from "taleshaper/provider/vllm";
const llm = new VllmProvider({
baseUrl: "http://127.0.0.1:8000/v1",
model: "meta-llama/Llama-3.1-8B-Instruct"
});Provider adapters are intended for server-side or local runtime use. Do not ship long-lived API keys to browser clients.
Imports
Most users can import from the package root:
import {
DryRunProvider,
TaleShaperSession,
storySchema,
type Envelope,
type Story
} from "taleshaper";Subpath imports are available when you want a narrower import surface:
import { storySchema } from "taleshaper/schema";
import type { LlmProvider } from "taleshaper/provider";
import { createOpenAIProvider } from "taleshaper/provider/openai";
import { TaleShaperSession } from "taleshaper/shaper";Core Types
Story
A story has an id, title, brief, character roster, and ordered scenes.
Scene
A scene is the minimum rewrite unit. Envelope values are aligned to scene order.
Envelope
An envelope has info and values. info defines what the curve means.
values is either null or one number per scene.
TaleShaperSession
The session owns story state, envelope state, regeneration targets, active LLM operation state, streaming previews, and save/load state.
License
MIT
