Skip to main content

AI-driven TUI choose-your-own-adventure

Project description

par-storygen

Table of Contents

PyPI - Python Version Runs on Linux | MacOS | Windows Arch x86-64 | ARM | AppleSilicon PyPI - License

"Buy Me A Coffee"

About

par-storygen is a TUI (Text UI) choose-your-own-adventure powered by configurable LLMs. The application was built with Textual and Rich. A configurable LLM (via pydantic-ai) drives theme, characters, narration, and choices; image providers render portraits and scene illustrations using per-character reference portraits for visual consistency. Game state is a content-addressed tree persisted as JSON — walk the same choices twice and the game replays byte-for-byte. It runs on all major OS's including Windows, macOS, and Linux.

Features

Core Capabilities

  • Multi-Provider Text: OpenAI, OpenRouter, and Ollama (all OpenAI-compatible) with per-save provider pinning
  • Multi-Provider Images: OpenAI (ref-aware), Gemini (ref-aware), Z.AI (text-to-image), and Ollama (local) with automatic fallback
  • Interactive Wizard: 8-step story setup — theme, tone, narration style, art style, length, reader level, characters, and confirmation
  • Half-Block Inline Art: Scene illustrations rendered directly in the terminal using reference portraits for character consistency
  • Content-Addressed Tree: Every choice is cached; replaying the same path returns byte-for-byte identical results
  • Save/Resume: Full game state persistence with --resume flag to pick up where you left off

Advanced Features

  • Branch Prefetch: Background-generates pending choices while you read, for instant picks
  • Character Library: Export characters from finished stories and re-import with optional AI-powered backstory adaptation
  • Character Outfits: Define multiple looks per character and switch between them mid-story
  • Endings Gallery: Card-based view of every ending reached, with jump-to-node navigation
  • Branch Replay: Read-only slideshow of any path from root to an explored node
  • Story Graph: Full tree view with marker legend, current-node arrow, and unexplored-choice leaves
  • Reference Images: Supply your own character images as portrait anchors for ref-aware providers
  • Reader Levels: Vocabulary and complexity controls for ages 0-5, 6-10, 11-15, or 15+
  • Settings Persistence: In-app Settings screen for provider defaults, art toggle, streaming, and prefetch options

Technical Excellence

  • Async Architecture: Non-blocking pipeline with concurrent illustration and portrait generation
  • Type Safety: Fully typed Python 3.13 codebase with strict pyright mode
  • XDG-Compliant Paths: Config and data stored per platform conventions
  • Atomic Persistence: All file writes use .tmp + os.replace for crash safety
  • Cost Tracking: Per-save image cost and token usage with per-model call counts
  • Prompt Caching: Static system prompt content for optimal API cache hit rates

Screenshots

Splash & Main Menu

Splash screen Main menu

Settings — Configure text and image providers, art toggles, streaming, and prefetch options. Preferences persist across sessions.

Settings screen

New Story Wizard — An 8-step guided setup for theme, tone, narration style, art style, length, reader level, and characters (with library import). See the full wizard walkthrough.

Wizard theme step

Load Story — Browse and resume existing saves.

Load story screen

Gameplay — The main play screen shows the scene illustration (half-block inline art) on the left, narrative text in the center, and the character roster on the right. Numbered choices appear at the bottom.

Story play screen Beat generation

Character Portraits — Press p to view full portraits for every character in the current scene. High-res zoom available for ref-aware providers.

Character roster

Story Graph — Press g for a full tree view with marker legend, current-node arrow, and unexplored-choice leaves. Press r on any node for branch replay.

Story graph

Character Catalog — A scrollable grid of exported characters from across all your stories. Each card shows the portrait thumbnail, name, and source story. Import any character into a new story (keep as-is or adapt backstory to the new theme).

Character catalog

High-res versions of images are available

High-res portrait

Prerequisites

For running

  • Install Python 3.13 or newer
    • https://www.python.org/downloads/ has installers for all versions
    • On Windows the Scoop tool makes it easy to install and manage things like python
      • Install Scoop then do scoop install python

For development

  • Install uv
  • Install GNU Compatible Make command
    • On Windows if you have scoop installed you can install make with scoop install make

Installing

Installing uv

If you don't have uv installed you can run the following:

curl -LsSf https://astral.sh/uv/install.sh | sh

Install from PyPI with uv

uv tool install par-storygen
storygen

Install from PyPI with pip

pip install par-storygen
storygen

Source install from GitHub

git clone https://github.com/paulrobello/par-storygen
cd par-storygen
make setup

Command line arguments

usage: storygen run [--resume]

par-storygen -- AI-driven TUI choose-your-own-adventure.

options:
  -r, --resume   Re-open last-played save

Environment Variables

Variables are loaded in the following order, last one to set a var wins

  • HOST Environment
  • .env file in project root
  • par-storygen Settings Screen

Text provider variables

  • OPENAI_API_KEY — OpenAI API key
  • OPENROUTER_API_KEY — OpenRouter API key
  • STORYGEN_TEXT_PROVIDERopenai (default), openrouter, or ollama
  • STORYGEN_TEXT_MODEL — Model identifier (default: gpt-4o-mini)
  • STORYGEN_TEXT_BASE_URL — Override base URL for the text provider

Image provider variables

  • GEMINI_API_KEY — Google Gemini API key
  • ZAI_API_KEY — Z.AI API key
  • STORYGEN_IMAGE_PROVIDERopenai (default), gemini, zai, or ollama
  • STORYGEN_IMAGE_MODEL — Model identifier (default: gpt-image-2)
  • STORYGEN_IMAGE_BASE_URL — Override base URL for the image provider
  • STORYGEN_IMAGE_API_KEY — Override API key for the image provider

See .env.example for the full list.

Data locations

par-storygen uses the XDG Base Directory Specification via the xdg-base-dirs library. Override the defaults by setting XDG_DATA_HOME or XDG_CONFIG_HOME.

Default data directory ($XDG_DATA_HOME/storygen/):

Platform Default path
macOS ~/.local/share/storygen/
Linux ~/.local/share/storygen/
Windows %APPDATA%\storygen\

Within the data directory:

  • games/<uuid>/ — one subdirectory per save, containing game.json and portrait/scene images
  • library/<uuid>/ — one subdirectory per exported character, containing character.json and portrait.png

Config state file ($XDG_CONFIG_HOME/storygen/state.json) stores provider preferences, art settings, and wizard defaults:

Platform Default path
macOS ~/.config/storygen/state.json
Linux ~/.config/storygen/state.json
Windows %APPDATA%\storygen\state.json

Running par-storygen

make run

Or directly:

uv run storygen

Resume last game:

make resume
# or
uv run storygen run --resume

Choosing a text model

par-storygen supports three text-LLM providers, all routed through pydantic-ai's OpenAI-compatible OpenAIChatModel.

There are two ways to select the provider and model:

  1. Environment variables (best for one-off runs, CI, or temporary overrides) — STORYGEN_TEXT_PROVIDER, STORYGEN_TEXT_MODEL, and optionally STORYGEN_TEXT_BASE_URL. See .env.example for the full list.
  2. Settings screen (persisted across sessions) — open the app, hit Settings from the main menu, and edit the "Text provider" block. Saved prefs live in $XDG_CONFIG_HOME/storygen/state.json.

Priority order: real environment variables > .env file > Settings-saved prefs > hardcoded default (openai / gpt-4o-mini).

OpenAI

Set OPENAI_API_KEY. Known-good models: gpt-4o-mini (default — fast, cheap, reliable structured output), gpt-4o (higher-quality prose), gpt-4.1-mini (newer, balanced). Billing runs through api.openai.com.

OpenRouter

Set OPENROUTER_API_KEY and STORYGEN_TEXT_PROVIDER=openrouter. OpenRouter routes to dozens of frontier models under a single key. Known-good models: anthropic/claude-3.5-sonnet (excellent structured output), meta-llama/llama-3.3-70b-instruct (open-weight, very cheap). Full catalog at openrouter.ai/models. Keep the <vendor>/<model> slash form.

Ollama

Set STORYGEN_TEXT_PROVIDER=ollama and run ollama serve locally (default: http://localhost:11434). No API key required. Known-good models: llama3.3:70b (good narration if you have the VRAM), qwen2.5:32b-instruct (lighter, faster). The model string must match the Ollama tag exactly. For remote Ollama, set STORYGEN_TEXT_BASE_URL=http://<host>:11434/v1.

Note: Each save pins its own text_config. Changing the provider in Settings only affects new stories — existing saves keep what they were created with.

Choosing an image model

par-storygen supports four image-gen providers. Only OpenAI and Gemini support reference images — the pattern used to keep characters visually consistent across scenes. Z.AI and Ollama are text-to-image only, so character consistency will drift.

There are two ways to select the image provider and model:

  1. Environment variablesSTORYGEN_IMAGE_PROVIDER, STORYGEN_IMAGE_MODEL, STORYGEN_IMAGE_BASE_URL, and STORYGEN_IMAGE_API_KEY. See .env.example.
  2. Settings screen (persisted across sessions) — edit the "Image provider" block in Settings.

Priority order: real environment variables > .env file > Settings-saved prefs > hardcoded default (openai / gpt-image-2).

OpenAI

Set OPENAI_API_KEY. Supports reference images natively via images.edit — each scene folds in the featured characters' portraits so faces stay consistent. Known-good models: gpt-image-2 (default), gpt-image-1.5 (previous gen), gpt-image-1 (older, cheaper — untested with current gpt-image-2-tuned prompts). Docs: platform.openai.com/docs/guides/images.

Google Gemini

Set GEMINI_API_KEY and STORYGEN_IMAGE_PROVIDER=gemini. Supports up to 14 reference images per call. Known-good models: gemini-3.1-flash-image-preview (Nano Banana 2, $0.067/1K image tokens), gemini-3-pro-image-preview (Nano Banana Pro, $0.134/image). Leave STORYGEN_IMAGE_BASE_URL blank for Gemini. Docs: ai.google.dev/gemini-api/docs/image-generation.

Z.AI GLM-image

Set ZAI_API_KEY and STORYGEN_IMAGE_PROVIDER=zai. Text-to-image only — no reference-image support. Price: $0.015/image. Known-good model: glm-image. Docs: docs.z.ai.

Ollama

Set STORYGEN_IMAGE_PROVIDER=ollama and run ollama serve locally. No API key required. macOS-only as of 2026-04, requires server ≥ 0.13.3. No reference-image support. Known-good models: x/z-image-turbo (fastest), x/flux2-klein:4b, x/flux2-klein:9b (higher quality, more VRAM). For remote Ollama, set STORYGEN_IMAGE_BASE_URL=http://<host>:11434/v1/.

Fallback provider

Settings lets you pick a secondary image provider that kicks in when the primary fails. Each fallback trip fires a toast. Useful combos: OpenAI primary + Gemini fallback (both ref-supporting). Same-provider fallbacks are ignored.

Note: Each save pins its own image_config. Changing the primary image provider in Settings only affects new stories.

Character library: exporting and importing

par-storygen keeps a cross-game character library at $XDG_DATA_HOME/storygen/library/ — one subdirectory per exported character holding metadata plus its portrait. Characters from finished (or in-progress) stories can be re-used in new stories without regenerating the portrait, saving both token cost and wall time.

Export

From a game's Portraits screen, press the Export button next to any character. The character's name, backstory, personality, physical description, portrait, and the exact portrait prompt are copied into the library. Re-exporting the same character creates a separate library entry — use the Library Browser to clean up.

Import

During the wizard's CHARACTERS step, press l to open the Library Browser. Pick a character, then choose:

  • Keep as-is — added to your new story unchanged. Portrait PNG is copied (no image-provider API calls).
  • Adapt to theme — an LLM rewrites only the backstory to fit your new story's theme. Name, personality, and physical description are preserved so the existing portrait still matches.

Delete + sort

The Library Browser has per-entry Delete buttons with confirmation. Press s to toggle sort between newest-first (default) and alphabetical.

Replay and endings

Once you've played through to one or more endings, two read-only views let you revisit the journey.

Endings gallery (e from PlayScreen)

Press e to open a card per ending you've reached. Each card shows the scene image, a narration excerpt, and the path of choices you took. Press Jump on any ending to set the playhead to that node.

The e binding is hidden when no endings have been reached yet.

Branch replay (r from GraphScreen)

Open the graph (g from PlayScreen), highlight any explored node, and press r to walk through every beat from root to that node. Use space/right/n to advance, left/p to go back, j to jump to live play at the current step, and escape to exit.

Replay is read-only — no regeneration, no LLM calls.

Branch prefetch

While you're reading a beat, par-storygen can background-generate the next beats for each pending choice. When you pick, the next beat appears instantly (no LLM call) — assuming the prefetch finished in time.

Enabling

Open Settings and toggle:

  • Enable branch prefetch — turns on background generation. Off by default (spends tokens up-front for paths you may never pick).
  • Prefetch scene images too — also runs scene-image generation for each prefetched beat. Off by default and disabled unless prefetch + global art are both on.

What gets generated

For each pending choice from the current beat:

  • Beat text (always, when prefetch is on).
  • Illustration plan (always — cheap, lets you press i later).
  • Scene image (only when "Prefetch scene images too" is on).

Behavior notes

  • Sibling prefetches keep running after you pick. They populate save.nodes, so alternate branches are free cache hits.
  • Failures are silent. If a provider is down, you only see an error if you pick a choice whose prefetch failed (then live generation retries).
  • Settings take effect on the next beat. Toggle any time; in-flight prefetches finish, future ones honor the new state.

Character outfits

Define multiple looks for a single character and switch between them at will. Each outfit is its own portrait, used as the reference image for scene generation so your character appears in the picked outfit across subsequent beats.

Creating an outfit

From PlayScreen, press p to open Portraits. For any character:

  1. Click Add outfit.
  2. Give it a short name (e.g. "ballroom gown", "armored", "swimwear").
  3. Write a one-sentence description (e.g. "wearing a flowing red gown with gold trim").
  4. Press Generate. The new outfit is generated using the character's existing physical description PLUS your outfit description.

Outfit thumbnails appear in a row under the character's main portrait.

Switching and deleting

Click any outfit thumbnail for a Set as current / Delete / Cancel menu. Setting an outfit updates the character's active portrait and makes scene generation use that outfit as the reference from now on.

The main (base) portrait is preserved. Press Revert to base (visible only when an outfit is active) to switch back. Deleting the currently-active outfit auto-reverts to base first.

Notes

  • Outfit generation uses the same image provider + model + cost as a regular portrait.
  • Image streaming is intentionally OFF for outfit generation (portraits are 5-10s — too fast for streaming to pay off).
  • Library export captures only the currently-active outfit. Imported characters start with an empty outfits list.

Contributing

Clone the repo and run the setup make target. Note uv is required.

git clone https://github.com/paulrobello/par-storygen
cd par-storygen
make setup

Please ensure that all pull requests are formatted with ruff, pass ruff lint and pyright. You can run the make target checkall to ensure the pipeline will pass with your changes.

make checkall    # ruff format + lint + pyright + pytest

The easiest way to setup your environment for smooth pull requests:

With uv installed:

uv tool install pre-commit

From repo root:

pre-commit install
pre-commit run --all-files

Roadmap

As of v0.1.0. See CHANGELOG.md for the full release history.

Where we are

  • Core Gameplay — Theme wizard, beat pipeline, choice tree, caching, save/resume
  • Multi-Provider — OpenAI, OpenRouter, Ollama for text; OpenAI, Gemini, Z.AI, Ollama for images
  • Visual Consistency — Reference portraits, half-block inline art, character outfits
  • Cross-Game Library — Export/import characters with optional AI backstory adaptation
  • Navigation — Story graph, endings gallery, branch replay
  • Branch Prefetch — Background beat generation for instant picks
  • Reader Levels — Vocabulary and complexity controls for different age ranges

Where we're going

  • Sound effects and music per scene
  • Multiplayer (shared story tree)
  • More image providers and art styles
  • Story templates and presets
  • Export stories as HTML/PDF

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

par_storygen-0.1.0.tar.gz (126.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

par_storygen-0.1.0-py3-none-any.whl (161.8 kB view details)

Uploaded Python 3

File details

Details for the file par_storygen-0.1.0.tar.gz.

File metadata

  • Download URL: par_storygen-0.1.0.tar.gz
  • Upload date:
  • Size: 126.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for par_storygen-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5e1cff4c182979905d0a1b432bfeb9212c63d90ae002b7ff1632a8b4d11716ab
MD5 a5ea184e33fdf4d6683a635a87c38fd8
BLAKE2b-256 96220bda91ac689651b757351391e6fd49707c6ff12391385b69d0dbe0176914

See more details on using hashes here.

Provenance

The following attestation bundles were made for par_storygen-0.1.0.tar.gz:

Publisher: release.yml on paulrobello/par-storygen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file par_storygen-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: par_storygen-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 161.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for par_storygen-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 777c09a9a38eecf5f135178e790c238e88e6dccc096736b58c563dd7b459e45d
MD5 3e1bc9dfc74d7aaf4de51bc1ec3ede36
BLAKE2b-256 d68fb66fe796a8e66ad8d3d9b47b6644e044f7b0ae517f1bf6fca4674a488357

See more details on using hashes here.

Provenance

The following attestation bundles were made for par_storygen-0.1.0-py3-none-any.whl:

Publisher: release.yml on paulrobello/par-storygen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page