Skip to main content

writing-studio

A git-backed writing environment for fiction: a CLI, a self-hosted server and a browser IDE that treat scenes as plain markdown and record who wrote every character.

Status
Active, private repository
First commit
Latest commit
  • Node.js
  • Fastify
  • CodeMirror
  • Vitest
  • Playwright
  • Git
  • MCP
Source: private repository
writing-studio architecture Three front ends (Claude Code writing plugin, the ws CLI, and a browser IDE) share one core package. The plugin drives the CLI, the browser talks to a Fastify server, and both CLI and server call core. Core reads and writes a project folder of markdown scenes, notes, provenance files, an AI ledger and a git repository, and calls LLM providers and MCP tools. Claude Code pluginwriting: proposals, pane ws CLIcommander Web IDECodeMirror, provenance lens drives LLM providersAnthropic, OpenAI-style, MCP ServerFastify, single-user auth HTTP core package scenes and notes | provenance | AI + ledger | search, rename, lint | EPUB and RTF export calls Project folder on diskmarkdown scenes, notes.json, provenance.json, ai/ledger.jsonl, git history writes; auto-commit unless opted out
Architecture overview: Architecture diagram. A Claude Code plugin drives the ws CLI; a browser IDE talks to a Fastify server; the CLI and server both call a shared core package, which calls LLM providers and reads and writes a project folder of markdown scenes, notes, provenance files, an AI ledger and git history.

The problem

I write fiction and I write code, and I kept wanting the guarantees code gets: every change recorded, nothing silently overwritten, and a clear answer to “who wrote this line?” Most writing tools hide history, and most AI writing tools blur authorship. I wanted a tool where an AI could help without ever being able to quietly rewrite my prose.

Key design decisions

Plain files, real git. Chapters and scenes are markdown files; notes sit beside them as JSON. Every save is committed to a git repository at the project root, so ws scene log, diff and restore are thin wrappers over something I can also drive with plain git. Restoring writes the old text back as a new commit rather than rewriting history. Trade-off: automatic commits are opinionated, so there is an opt-out (STUDIO_AUTOCOMMIT=0 or a per-project setting), and the environment variable wins.

One core, three front ends. A shared core package holds the data model, version history, AI providers, search, rename, lint and EPUB/RTF export. The CLI, a Fastify server and the web IDE all sit on top of it, which kept behaviour identical across them. The cost is a monorepo with several test suites to keep green.

Provenance per character. The web IDE labels every run of text as human, AI (linked to its ledger entry), pasted or external, and the server replays each edit so it, not the browser, is the authority.

AI proposes, the writer decides. AI output arrives as proposals, and each proposal and how it was resolved is appended to a ledger file. Providers are model-agnostic (Anthropic, any OpenAI-compatible endpoint, or a local stub that needs no key), and an agentic mode can call MCP tools.

Stack

Node 18+ ESM, commander, Fastify, CodeMirror, Vitest and Playwright. No database: the project folder is the datastore.

Outcome and what I would change

The repo has 42 commits between 2026-07-23 and 2026-10-12. Running the suites locally, I counted 495 passing Node tests across the CLI, core and server, plus 408 passing Vitest tests in the web app; there is also a Playwright end-to-end suite that I did not run for this write-up.

Honest gaps: the built-in terminal editor is still rough (it does not handle wide characters the way a real editor does, which is why external editors are the default), and a screenshot or demo has not been added yet. The repository is private for now. If I started again I would write the provenance model first, since the web IDE, the ledger and the Claude Code plugin all grew around it.