{"id":51831077,"url":"https://github.com/danielbodart/talebrary","last_synced_at":"2026-07-22T15:03:54.096Z","repository":{"id":369094193,"uuid":"796300434","full_name":"danielbodart/talebrary","owner":"danielbodart","description":"An interactive fiction platform for discovering and playing 2,000+ text adventure games. Browse a curated catalogue, play classics like Zork in the browser, and enjoy AI-generated cover art and illustrations throughout.","archived":false,"fork":false,"pushed_at":"2026-07-03T14:42:13.000Z","size":37636,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-07-03T16:19:29.516Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://talebrary.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielbodart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-05-05T14:43:18.000Z","updated_at":"2026-07-03T14:47:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/danielbodart/talebrary","commit_stats":null,"previous_names":["danielbodart/talebrary"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/danielbodart/talebrary","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Ftalebrary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Ftalebrary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Ftalebrary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Ftalebrary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielbodart","download_url":"https://codeload.github.com/danielbodart/talebrary/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielbodart%2Ftalebrary/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35766436,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-22T15:03:53.070Z","updated_at":"2026-07-22T15:03:54.084Z","avatar_url":"https://github.com/danielbodart.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Talebrary\n\n[**talebrary.com**](https://talebrary.com/) — an interactive fiction platform for discovering and playing over 2,000 tales in the browser, with AI-generated cover art, scene illustrations, and intelligent command suggestions.\n\n## AI Pipelines\n\nEvery game in the catalogue gets unique AI-generated artwork, and players get real-time AI assistance during gameplay. This is powered by three distinct pipelines, each orchestrating multiple Cloudflare Workers AI models.\n\n### Cover Art Generation\n\nEach game's cover art is produced by a durable workflow that chains text and image models with a multi-strategy fallback:\n\n**When the game has existing cover art** (from IFDB):\n\n1. **Store original** — Fetch and persist the source image in R2\n2. **Style transfer** — Pass the original through [FLUX.2 Klein](https://developers.cloudflare.com/workers-ai/models/flux-2-klein-9b/) (img2img) with a graphic-novel style prompt\n3. **LLM-enhanced style transfer** (fallback) — If plain style transfer fails, use [Llama 3.3 70B](https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/) to extract a visual scene description from the game's metadata, combine it with the style prompt, and retry FLUX.2 with the richer context\n4. **Pure generation** (final fallback) — Generate a fresh image with [Leonardo Phoenix](https://developers.cloudflare.com/workers-ai/models/leonardo-phoenix-1.0/) using just the title\n\n**When there's no existing art:**\n\n1. **Scene extraction** — Llama 3.3 reads the game's title and description to produce an image prompt\n2. **Image generation** — Leonardo Phoenix generates the cover art from that prompt\n\nThe pipeline always produces something — the fallback chain ensures no game goes without cover art.\n\n### Scene Illustrations\n\nDuring gameplay, the illustration workflow generates contextual artwork for the current scene:\n\n1. **Prompt generation** — Llama 3.3 receives the full scene context: the story's title and description, the current scene, and optionally the previous scene. It generates an image prompt that's consistent with the narrative — if the previous scene was underground, the new illustration won't suddenly be outdoors unless the text says so.\n2. **Image generation** — Leonardo Phoenix renders the scene. The model can be overridden per-request for experimentation.\n\nThe LLM returns a 404 status (in JSON) when a scene has no visual content worth illustrating — the workflow gracefully falls back to a stylised book cover rather than generating a meaningless image.\n\n### Command Suggestions\n\nThe suggestions pipeline powers an interactive command UI during gameplay. Llama 3.3 analyses the current scene and returns a structured `SuggestionTree` — a mapping of verbs to applicable nouns extracted from the scene description:\n\n```json\n{\n  \"people\": true,\n  \"tree\": {\n    \"examine\": [\"atrium\", \"library\", \"bookcases\", \"desk\", \"sandwich\"],\n    \"ask\": [\"librarian\"],\n    \"take\": [\"sandwich\"],\n    \"open\": [\"drawer\"],\n    \"east\": [],\n    \"inventory\": []\n  }\n}\n```\n\nThe LLM works from a fixed command vocabulary (directions, object interactions, people commands, game controls) and only includes commands and nouns that are relevant to the current scene, sorted by usefulness.\n\n### Models\n\n| Model | Type | Used For |\n|-------|------|----------|\n| [Meta Llama 3.3 70B](https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/) | Text (instruct) | Scene extraction, illustration prompts, command suggestions |\n| [FLUX.2 Klein 9B](https://developers.cloudflare.com/workers-ai/models/flux-2-klein-9b/) | Image (img2img) | Style transfer on existing cover art — always uses multipart/form-data |\n| [Leonardo Phoenix 1.0](https://developers.cloudflare.com/workers-ai/models/leonardo-phoenix-1.0/) | Image (text-to-image) | Cover art generation, scene illustrations |\n\n## Caching Architecture\n\nAI generation is expensive, so every generated asset is cached through multiple layers to ensure models are only ever called once per resource.\n\n```\nBrowser cache (ETag / If-None-Match → 304)\n  └─ Cloudflare CDN (public, max-age=60, stale-while-revalidate=600)\n       └─ ETag middleware (automatic MD5-based ETags on all responses)\n            └─ Cache-Control middleware (default public caching policy)\n                 └─ R2 write-through cache (BucketCachingHandler)\n                      └─ Origin handler (AI workflow / IF Archive / etc.)\n```\n\n**R2 as a write-through cache** — `BucketCachingHandler` wraps the AI handlers for cover art, illustrations, suggestions, and story files. On first request, it calls the origin, stores the result in R2 (keyed by URL path + MD5 of query params), and returns it. Subsequent requests are served directly from R2 with full ETag support for conditional requests. A `?reload` parameter forces cache bypass and regeneration.\n\n**HTTP caching middleware** — Every response flows through two decorators: `CacheControlHandler` stamps a default `public, max-age=60, stale-while-revalidate=600` policy, and `EtagHandler` computes strong ETags and handles `If-None-Match` → 304 short-circuiting. These work together with Cloudflare's CDN edge caching.\n\n**Original cover art** gets `max-age=31536000` (1 year) — it's fetched once from the source and never changes. Generated assets get shorter TTLs but are effectively permanent in R2.\n\n## Cloudflare Workers Platform\n\nThe production deployment runs entirely on Cloudflare's edge platform:\n\n| Service | Purpose |\n|---------|---------|\n| **Workers** | Application runtime — server-rendered HTML, API endpoints, routing |\n| **Workers AI** | All model inference (Llama 3.3, FLUX.2, Leonardo Phoenix) |\n| **D1** | Game catalogue database (synced from IFDB) |\n| **R2** | Object storage for runtime-written content — generated images and cached story files |\n| **Workers Static Assets** | Build-time client assets (CSS/JS/HTML/images/wasm), bundled and versioned with each deploy |\n| **Durable Objects** | IF Archive proxy — routes all ifarchive.org requests through a US-East data centre to avoid UK geo-blocking (HTTP 451) |\n| **Workflows** | Durable orchestration of multi-step AI pipelines with step-level persistence |\n\n### Portable Workflow Abstraction\n\nThe AI pipelines use a workflow abstraction that's decoupled from Cloudflare:\n\n```typescript\ntype Workflow\u003cParams, Result\u003e = (params: Params, step: Step) =\u003e Promise\u003cResult\u003e;\n```\n\n`Step.do(name, fn)` maps directly to Cloudflare's `WorkflowStep.do()` in production, giving durable execution with step-level persistence. In local dev and tests, `DirectRunner` executes workflows synchronously with `InMemoryStep`. The workflow code itself has no Cloudflare imports — it's just async functions with dependency injection.\n\n## WebAssembly — Playing Text Adventures in the Browser\n\nThe in-browser player is powered by [**wasiglk**](https://github.com/bodar/wasiglk) ([JSR](https://jsr.io/@bodar/wasiglk)) — a companion library that compiles classic interactive fiction interpreters to WebAssembly. It supports 17 interpreters covering virtually every IF format ever created:\n\n| Interpreter | Formats |\n|-------------|---------|\n| Fizmo | Z-machine v1-8 (.z1-.z8, .zblorb) |\n| Glulxe, Git | Glulx (.ulx, .gblorb) |\n| Hugo | Hugo (.hex) |\n| TADS 2, TADS 3 | TADS (.gam, .t3) |\n| Alan 2, Alan 3 | Alan (.acd, .a3c) |\n| Scare | ADRIFT (.taf) |\n| Agility | AGT (.agx) |\n| AdvSys, Level 9, Magnetic, Scott, Plus, Taylor, JACL | Various legacy formats |\n\nThe interpreters are compiled from their original C sources using **Zig as a cross-compiler** targeting `wasm32-wasi`. They run in a **Web Worker** with a WASI shim providing the system interface. The key challenge is that these interpreters are synchronous C programs that block on `read()` for player input — this is solved using **JSPI** (JavaScript Promise Integration), which allows the WebAssembly execution to suspend at the `fd_read` WASI call and resume when the player's input arrives as a resolved Promise.\n\nCommunication between the interpreter and the browser uses the **RemGlk JSON protocol** — the WASM module reads JSON input events and writes JSON update objects describing window layout, text content, and input requests. Custom elements (`\u003cinteractive-fiction\u003e`, `\u003cbuffer-window\u003e`, `\u003cgrid-window\u003e`, `\u003cuser-input\u003e`) render the game UI on the main thread.\n\n## Architecture\n\nTwo runtimes, one codebase:\n\n- **Bun** for local development (`src/bun/app.ts`)\n- **Cloudflare Workers** for production (`src/cloudflare/app.ts`)\n\nBoth share the same application logic via `Application.ts`, which wires everything together using dependency injection (`@bodar/yadic`). The only differences are the concrete implementations: SQLite vs D1, folder vs R2, direct workflow execution vs Cloudflare Workflows.\n\n`Http` is the universal type: `(request: Request) =\u003e Promise\u003cResponse\u003e`. Handlers are composed by decorating this function — no frameworks, no middleware stacks.\n\n### The Catalogue\n\nThe catalogue uses a library metaphor:\n\n- **Atrium** (`/`) — the entrance hall with search and links to each wing\n- **Wings** (`/catalogue/{wing}`) — genre collections (Fantasy, Sci-Fi, Horror, etc.) and special collections (Top-Rated, Classics, Recent)\n- **Aisles** (`/catalogue/{wing}/{aisle}`) — individual category pages listing games\n\nEach level is a server-rendered HTML page with AI-generated scene illustrations.\n\n## Project Structure\n\n```\nsrc/\n  ai/            AI abstraction (TalebraryAi interface + adapters)\n  workflows/     Portable workflow definitions (cover art, illustration)\n  prompts/       LLM and image prompt templates\n  storage/       Bucket abstraction (R2 / folder) + BucketCachingHandler\n  http/          Http type, URI parsing, ETag + Cache-Control middleware\n  catalogue/     Atrium, Wing, Aisle handlers\n  content/       Game detail, cover art, story, suggestions, illustrations\n  player/        Client-side custom elements for the IF player\n  templates/     Base template, slot processing, renderers\n  games/         Game finder queries\n  database/      Database abstraction (D1 / SQLite)\n  bun/           Local dev composition root\n  cloudflare/    Production composition root + Durable Object/Workflow entrypoints\n  events/        Event tracking\n  system/        Clock, timers, digest utilities\nwww/             Compiled client assets\ndb/              Database schema and sync tooling\ntest/            Tests (mirrors src/ structure)\n```\n\n## Development\n\n```bash\n./run start          # Bun dev server with file watching\n./run startw         # Wrangler dev with remote bindings (real D1, R2, AI)\n./run test           # Run all tests\n./run check          # TypeScript type checking\n./run build          # Full build (check + compile + test)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielbodart%2Ftalebrary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielbodart%2Ftalebrary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielbodart%2Ftalebrary/lists"}