No description
  • TypeScript 92.5%
  • JavaScript 5.8%
  • HTML 1.7%
Find a file
bengo feb6dace3c
Some checks failed
/ test (push) Successful in 29s
/ deploy (push) Failing after 48s
wip
2026-03-02 23:40:38 -08:00
.changeset wip 2026-03-02 23:40:38 -08:00
.forgejo/workflows fix order of docs build 2026-02-28 09:10:22 -08:00
.storybook init 2026-02-28 06:04:52 -08:00
docs workingimportmap 2026-02-28 08:54:25 -08:00
src wip 2026-03-02 23:38:43 -08:00
stories wip 2026-03-02 23:40:38 -08:00
.eleventyignore init 2026-02-28 06:04:52 -08:00
.gitignore deploy storybook 2026-02-28 09:06:06 -08:00
CHANGELOG.md changeset release 2026-02-28 06:05:12 -08:00
cli.ts init 2026-02-28 06:04:52 -08:00
DEVELOPMENT.md init 2026-02-28 06:04:52 -08:00
eleventy.config.js deploy storybook 2026-02-28 09:06:06 -08:00
index.html init 2026-02-28 06:04:52 -08:00
package-lock.json wip 2026-02-28 07:15:39 -08:00
package.json wip 2026-03-02 23:40:38 -08:00
PLAN.md init 2026-02-28 06:04:52 -08:00
README.md better readme 2026-02-28 06:37:15 -08:00
tsconfig.build.json wip 2026-02-28 07:15:39 -08:00
tsconfig.json init 2026-02-28 06:04:52 -08:00
typedoc.json init 2026-02-28 06:04:52 -08:00
vite.config.ts build pan-saga 2026-02-28 08:51:32 -08:00
vitest.config.ts init 2026-02-28 06:04:52 -08:00
vitest.unit.config.ts init 2026-02-28 06:04:52 -08:00

pan-saga

<pan-saga> reads screenplay-style saga scripts and streams them as JSON activity objects — characters speaking, narrator descriptions, cinematic effects. Drop it in any HTML page, point it at a .saga file or write one inline, and iterate the results.


Usage

Inline source (slot)

<pan-saga>
  <script slot="src" type="text/saga">
    # Int. Bengo Apt.

    @ Bengo
    > if you omit the hyphen it'll redirect you to

    <hello-world

    ! this is a comment  it is ignored

    ^ fade to black
  </script>
</pan-saga>

Remote source (attribute)

<pan-saga src="https://example.com/story.saga"></pan-saga>

If src is provided but the fetch fails, <pan-saga> falls back to the inline slot="src" script tag if one is present.

Saga format sigils

Sigil Meaning Emitted activity
# … Scene heading / location (sets location context)
@ … Character name (sets speaker context)
& … Character mood/modifier (sets mood context, silent)
> … Dialogue CreateActivity
^ … Cinematic effect EffectActivity
< tagname Embedded HTML element (attrs follow) NarrateActivity
! … Comment (ignored)
plain text Narrator description NarrateActivity

Attribute lines for <tagname elements look like key: value or key: (boolean attribute). A blank line ends the attribute block.

Activity shapes

// dialogue
{
  "type": "Create",
  "actor": { "name": "Bengo" },
  "object": { "type": "Note", "content": "if you omit the hyphen it'll redirect you to" },
  "location": "Int. Bengo Apt."
}

// narrator text or embedded HTML element
{
  "type": "Narrate",
  "actor": { "type": "Narrator" },
  "content": "<hello-world>"
}

// cinematic effect
{
  "type": "Effect",
  "content": "fade to black"
}

Element API

Implemented in src/pan-saga.ts as PanSaga extends LitElement. The package has no side effects — you register the element yourself when your app is ready.

HTML attributes

Attribute Type Description
src string URL to fetch saga text from. Falls back to inline slot content on fetch failure.

Slots

Slot Description
<script slot="src" type="text/saga"> Inline saga source text. Used as primary source if no src attribute, or as fallback if the remote fetch fails.

JavaScript / TypeScript

import { PanSaga } from "pan-saga";

// Registration is the caller's responsibility — the library has no side effects:
customElements.define("pan-saga", PanSaga);

PanSaga#activities: AsyncIterable<Activity>
Lazy async iterable of parsed Activity objects. Begins resolving once the element is connected to the DOM and the saga source is ready. Defined in src/types.ts.

const el = document.querySelector("pan-saga") as PanSaga;
for await (const activity of el.activities) {
  console.log(activity); // CreateActivity | NarrateActivity | EffectActivity
}

The parser itself is a standalone export — usable without the element:

import { parseSaga } from "pan-saga/parser";

for await (const activity of parseSaga(sagaText)) {
  console.log(activity);
}

The parser is a streaming async generator — it emits one activity at a time and never buffers the full file. For streaming directly from a fetch response body, see SagaTransformStream in DEVELOPMENT.md.


CLI

Pipe saga text in, get NDJSON activity objects out:

printf '@ Alice\n> Hello world\n^ fade to black' | pan-saga | jq .
npx pan-saga --help   # usage + sigil reference

Requires Node 22+.


Quick start

npm run dev    # dev server → http://localhost:5173
npm run build  # production bundle
npm test       # browser tests via vitest + Storybook

See DEVELOPMENT.md for full setup instructions, module layout, tool notes, Storybook details, and how to add tests.


Releases

Versioning is managed with Changesets. See DEVELOPMENT.md for the release workflow.