TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
Self-hosted AI writing workspace for long-form fiction: Codex-tracked character/place continuity, RAG-backed scanning, world-building chats, and propose-approve AI throughout. Fork of The Story Nexus.
ProductivityTypeScript · MDXGitHub @bloodgrvOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Loading the full README. The saved excerpt is shown below.
A self-hosted, AI-assisted writing workspace for long-form fiction, built around continuity: a Codex that tracks concrete character/place state over time (wardrobe, wounds, items, custom fields) with full snapshot history, a hybrid RAG index + scanner that checks your manuscript against it, and a set of purpose-built chat desks (world-building, outline, research, brainstorming, notes, and the main editor) — all gated behind an explicit propose → review → approve loop, never a silent AI write.Read the full README ↗A self-hosted, AI-assisted writing workspace for long-form fiction, built around continuity: a Codex that tracks concrete character/place state over time (wardrobe, wounds, items, custom fields) with full snapshot history, a hybrid RAG index + scanner that checks your manuscript against it, and a set of purpose-built chat desks (world-building, outline, research, brainstorming, notes, and the main editor) — all gated behind an explicit propose → review → approve loop, never a silent AI write.
Story Labyrinth is a local-first, single-tenant web application for writers who want AI woven into a real writing workflow without losing control of their own canon. It runs as one Express + SQLite server; every device on your network (or Tailscale tailnet) reaches the same story data through a browser, no separate desktop client. Deploy it with Docker (including a ready-made Unraid template) or run it directly with Node.
Codex & continuity
sqlite-vec, with an optional fully local in-process embedding model — no external API key needed) powers both chat context and a dedicated continuity Scanner, chapter-scoped or story-wide, running as a background job with resumable scansWriting surfaces
AI & extensibility
Everything else
better-sqlite3), Drizzle ORM, sqlite-vec (hybrid FTS5 + vector RAG)@huggingface/transformers for the optional local in-process embedding model, @modelcontextprotocol/sdk for MCP tool connections (client + server)node:22-slim; canvas — used for PDF image extraction on document import — needs Cairo/Pango/JPEG/GIF/RSVG system libs if you're not using Docker)Clone the repository
Install dependencies:
npm install
Start both backend and frontend servers:
npm run dev
This runs both:
http://localhost:3001http://localhost:5173 (with proxy to backend)Open http://localhost:5173 in your browser
npm run dev:server # Backend only (Express + SQLite)
npm run dev:client # Frontend only (Vite)
Build both backend and frontend:
npm run build
Start production server:
npm start
App runs on http://localhost:3000 (configurable via PORT environment variable)
npm run db:generate # Generate migration from schema changes
npm run db:migrate # Apply migrations to database
Pull and run the latest published image from Docker Hub:
docker-compose up -d
Supports linux/amd64, linux/arm64, and linux/arm/v7 architectures.
Access on http://localhost:3000 or from any device on your network using your machine's IP address.
Database persists in ./data/story-labyrinth.db (mounted volume).
Security note: by default the container publishes its port on every network interface, so it's reachable from anywhere on your LAN — this is intentional (see Remote Access via Tailscale below for the "no Tailscale" alternative too: direct LAN access is a fully supported setup here, not just a fallback). If this host is also reachable from somewhere you don't want the login screen exposed to — the public internet, a shared/untrusted network — set BIND_HOST=127.0.0.1 in your .env to bind loopback-only (reach it via a reverse proxy or SSH tunnel instead), or use docker-compose.tailscale.yml, which never publishes a host port at all. If you do put a reverse proxy or Tailscale Serve in front with real HTTPS, also set COOKIE_SECURE=true so the session cookie is marked HTTPS-only — leave it unset for the default plain-HTTP LAN/Tailscale setup.
Version pinning:
# docker-compose.yml
services:
story-labyrinth:
image: bloodgrv/story-labyrinth:0.6.0 # Pin to specific version
# or: bloodgrv/story-labyrinth:0.6 # Auto-update patches
# or: bloodgrv/story-labyrinth:latest # Latest release
Build and run from source:
docker-compose -f docker-compose.dev.yml up --build
A ready-made Community Applications template is included — in Unraid's Docker tab, Add Container → Template → paste:
https://raw.githubusercontent.com/bloodgrv/story-labyrinth/main/unraid/story-labyrinth.xml
It maps the WebUI port and an appdata volume, and sets the container to run as 99:100 (Unraid's own default appdata ownership) — this image doesn't do linuxserver.io-style dynamic PUID/PGID remapping at startup, so if you use a different appdata owner, update both the container's user and the folder's ownership together.
Rather than exposing the container to your LAN or the public internet, you can make it reachable only to devices on your Tailscale network (tailnet). This pairs well with the app's Owner/Editor/Viewer login system: Tailscale gates who can reach the server at all, and the app's own login gates what they can do once they're on it.
docker-compose.tailscale.yml runs a Tailscale sidecar container alongside the app and puts the app on the sidecar's network - it is never published on a host port, so it's unreachable except via the tailnet.
cp .env.tailscale.example .env
# edit .env and set TS_AUTHKEY
docker-compose -f docker-compose.tailscale.yml up -d
http://<TS_HOSTNAME>:<APP_PORT> (defaults to http://story-labyrinth:3000).Optional - proper HTTPS: by default this serves plain HTTP (the WireGuard tunnel itself is already encrypted, so this is not sending anything in the clear - it's just a browser padlock/UX nicety). To get a real https:// URL with a valid cert via Tailscale's own TLS, run once after the stack is up:
docker exec story-labyrinth-tailscale tailscale serve --bg https / http://localhost:3000
This persists in the state volume (./tailscale-state), so it survives container restarts.
If you're running npm start directly on a machine that already has tailscaled installed and running, no compose changes are needed - just point Tailscale's built-in reverse proxy at the app:
tailscale serve --bg https / http://localhost:3000
RF0/RF1/RF2/RF3/RF5 shipped; RF4 (optional Owner TOTP) not built: docs/Remote_Access_Funnel_Design.md.
Use Funnel when you need the app from a machine that cannot run Tailscale cleanly (e.g. work PC). Anyone with the URL reaches the login screen — this is not multi-tenant public hosting. Prefer Serve for devices already on your tailnet.
Before enabling:
BIND_HOST=127.0.0.1 or docker-compose.tailscale.yml (no host port).COOKIE_SECURE=true (Funnel is HTTPS).Bare metal (app on loopback):
tailscale funnel --bg https / http://127.0.0.1:3000
tailscale funnel status
# turn off when finished: tailscale funnel reset # or version-specific off
Docker sidecar (after docker-compose.tailscale.yml is up):
docker exec story-labyrinth-tailscale \
tailscale funnel --bg https / http://localhost:3000
Limits: public hostname stays *.ts.net; listen ports 443 / 8443 / 10000 only; bandwidth caps apply.
In-app: left sidebar Remote toggle above Logout — stricter session on this browser (1 day max, 1 hour idle). It does not start or stop Funnel on the host. An Instance label (Settings → Users) shows on the login page so you can confirm you're on the right server.
Shipped hardening: durable login lockout + IP throttle (RF1), owner "Revoke all sessions" (RF2), the 1-day/1-hour session policy + toggle above (RF3), login instance label (RF5). Optional TOTP / Cloudflare Access (RF4) still parked — see the design doc.
New releases are published via GitHub Releases, which automatically builds and pushes multi-architecture Docker images to Docker Hub.
Creating a release:
Using the PowerShell script (Windows):
.\release.ps1 patch # 0.6.0 → 0.6.1
.\release.ps1 minor # 0.6.0 → 0.7.0
.\release.ps1 major # 0.6.0 → 1.0.0
The script automatically bumps the version, pushes to GitHub, and opens the release page in your browser.
Manual process:
npm version <patch|minor|major>git push && git push --tagsResult - Workflow automatically builds and pushes Docker images with tags:
bloodgrv/story-labyrinth:0.7.0 (specific version)bloodgrv/story-labyrinth:0.7 (minor version)bloodgrv/story-labyrinth:0 (major version)bloodgrv/story-labyrinth:latestbloodgrv/story-labyrinth:sha-abc1234 (commit hash)Note: Docker images are only built on releases, not on every commit to main.
14 themes ship (plus a "System" option that follows your OS) — a few of them, to show the range:
| Dashboard — Black & Sand | Editor + docked chat — Midnight |
| Lorebook/Codex — Ember | Story Timeline — Mid Slate |
| Lorebook/Codex — Light | Lorebook/Codex — Forest |
server/ - Express.js API server
db/ - Database schema, client, migrations, and seedingroutes/ - API route handlers (stories, chapters, lorebook, codex, chats, rag, storyTimeline, storyGraph, storyMaps, agentJobs, mcpConnections, mcpServer, etc.)services/ - Business logic (RAG indexing/scanning, chat context assembly, AI client routing, background jobs, and one service per major feature)index.ts - Server entry pointsrc/features/ - Feature modules, one directory per major capability: lorebook, chat, chapters/chapter-versions/chapter-history, outline, story-timeline, story-maps, story-graph (Relationship Graph), rag-scanner, ai-review, agent-memory (Project Memory), mcp, brainstorm, notes, playbooks, name-generator, auto-humanizer, tts, trash, and more
*/hooks/ - TanStack Query hooks for data fetching*/pages/ - Route components*/components/ - Feature-specific UI componentssrc/components/ - Reusable UI componentssrc/Lexical/ - Text editor implementation (custom Lexical editor)src/services/ - Services (AI, database, export utilities, API client)src/types/ - TypeScript type definitionssrc/lib/ - Utility functions and helpersExport and import your entire database from Settings → Data:
Export: Downloads a JSON file containing all your data (stories, chapters, prompts, lorebook entries, etc.)
Import: Replaces all current data with data from a JSON file. System prompts are preserved.
Migration workflow:
Export and import individual prompts from the Prompts Manager UI:
Export: Downloads non-system prompts only (system prompts excluded)
Import: Validates and creates imported prompts as non-system (editable). Duplicate names get (Imported) suffix. New IDs and timestamps generated.
Format:
{
"version": "1.0",
"type": "prompts",
"prompts": [
/* array of prompt objects */
]
}
This fork has grown far beyond a single TODO list — current priorities and what's already shipped are tracked in docs/CURRENT_BACKLOG.md, with the reasoning behind load-bearing decisions in DECISIONS.md. Both are kept current; trust them over anything else if they disagree.