Manuskript ships a Docker setup whose default command is pytest
A open-source tool for writers
At a glance
- What is it?
- Manuskript is a GPL writing application in Python3 and PyQt5, with outline mode, index cards, distraction free writing and an open text save format. The repository documents almost none of that itself: the install step lives on a website, most feature documentation is a set of 2016 blog posts, and the container it provides starts the test suite rather than the editor.
- Who is it for?
- Manuskript fits a writer who wants an offline desktop editor with an open and plain text save format, an outline or index card workflow, and export to HTML, ePub, OpenDocument and DocX, and it costs nothing to try because the licence is GPL version 3 or later. Four things to weigh first.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 34 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The install step is a website link, not a command
Nothing in the repository tells you how to install the program. The Download section is one sentence, a link to theologeek.ch/manuskript/download, and the HowTo's section sends you to the project wiki for detailed instructions on how to install and use it. No package name, no version constraint, no shell line.
What the file does give you is the stack. Manuskript is written in Python3 and PyQt5, it runs on GNU/Linux, Mac OS X and Windows, and the three badges in the header point at a pytest workflow, the Snapcraft store and a Weblate translation project. So a snap exists, translations are managed on Weblate, and there is a test workflow, but the README never names the snap name, the Python version or the Qt version.
The requirements file is where the stack becomes concrete, and it pins all five of its entries exactly: lxml==6.1.0, pyenchant==3.2.2, PyQt5==5.15.11, PyQt5-Qt5==5.15.17 and PyQt5_sip==12.17.0. pyenchant is the binding that matters most for the spell checker the feature list promises, and Enchant underneath it is a C library that pip cannot supply.
The compose service builds an image and then runs the tests
The docker-compose.yml in the root defines one service, and it is not the editor.
services:
manuskript-builder:
build: .
image: manuskript-builder:latest
volumes:
- .:/workspace
environment:
- QT_QPA_PLATFORM=offscreenThe service is named manuskript-builder, it builds from the Dockerfile beside it, mounts the repository at /workspace and sets QT_QPA_PLATFORM to offscreen so that a Qt application can run without a display. What the Dockerfile hands that service is a test command:
CMD ["pytest", "-vs", "-o", "cache_dir=/tmp/pytest_cache", "--ignore=rpmbuild", "--ignore=dist"]So the container exists to run the suite headless, not to serve the writing application, and nothing in it starts Manuskript. The two ignore flags also tell you where packaging output lands: rpmbuild and dist are produced by the repository's own build scripts and are deliberately excluded from test collection.
One detail makes the mount load bearing. The Dockerfile never copies the application into the image, so before it runs apt, pip and pytest there is no source in /workspace at all. The bind mount is not a convenience here, it is the only thing that puts the code in front of pytest.
The image gives root a passwordless sudo rule for packaging
The build installs a wide set of system packages before any Python code runs: python3, pip, build-essential, qtbase5-dev, qt5-qmake, libxml2-dev, libxslt1-dev, mesa-utils, libgl1, libxcb-xinerama0-dev, xvfb, enchant-2, libenchant-2-dev, hunspell, rsync, dpkg-dev, rpm and sudo. The comment on that line explains the choice of apt packages: installing pre-compiled PyQt5, lxml and PyEnchant avoids compiling PyQt5 from source on aarch64 and ARM64.
Then it creates a sudoers entry.
RUN echo "root ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/nopasswdThe comment above it says this is needed by package/create_deb.sh, which runs sudo chown. So the reason for the rule is the Debian packaging script in the package directory, and the reason that script needs to chown is that packaging artefacts belong to root after a build. The practical effect is that anything running in this container, including a test that shells out, is root with unrestricted sudo.
That is a normal shape for a disposable build image and a poor shape for anything else, so the distinction matters when you read the file: the container is a packaging and test environment, not a runtime you would expose to a manuscript.
Two dependency channels install three different spell checkers
requirements.txt and the Dockerfile do not agree about what the application needs. The manifest pins five packages, all exact: lxml, pyenchant, PyQt5, PyQt5-Qt5 and PyQt5_sip. The image skips pip for those and takes the distribution versions instead, python3-pyqt5, python3-lxml and python3-enchant from apt, then installs a second, unpinned set through pip:
RUN pip3 install --no-cache-dir pyinstaller pytest pytest-faulthandler markdown language_tool_python symspellpy pyspellcheckerThree spell checking libraries appear in that list and none of them is in requirements.txt. language_tool_python, symspellpy and pyspellchecker are pure Python alternatives, sitting alongside pyenchant, which is the binding the manifest actually declares and which needs the C library, its headers and a hunspell dictionary, all three of which the image installs separately as enchant-2, libenchant-2-dev and hunspell.
So the repository carries two spell checking strategies at once, and only one of them is pinned. A reader who installs from requirements.txt on a bare desktop system gets pyenchant and then has to supply an Enchant library by hand, and has none of the three fallback checkers the image provides.
The newest tag predates the pins by more than a year
The release history is 0.17.0 on 2025-06-30, 0.16.1 on 2023-12-14 and 0.16.0 on 2023-12-07, so the 0.16 line moved a patch and then a minor inside a week in December 2023 and stopped until the following June. The default branch is develop, and the last push to it was 2026-09-01.
That is roughly fourteen months of commits after the newest tag. Anything a user gets by installing 0.17.0 predates those commits, while requirements.txt, the Dockerfile and docker-compose.yml live in the untagged tip of the branch. So the pinned versions of lxml, PyQt5 and pyenchant describe the working tree, not the release, and there is no tag that says which combination of a version and a pin set was tested together.
The CHANGELOG.md file at the root is the place that would resolve this, and the tree listing does not tell you whether it has an entry for work done after 0.17.0.
Every feature link points back to a 2016 post
The feature list is the most complete part of the README, and it links each item outward. Outline mode and index cards both point to theologeek.ch posts dated 2016-02-05. The story line view points to 2016-02-28. The frequency analyzer points to 2016-02-08. Fiction and non-fiction templates and writing modes point to 2016-03-31, the same day as the post on the open and plain text save format.
Distraction free mode and the item tracking feature link into the GitHub wiki instead, and the import and export list names HTML, ePub, OpenDocument and DocX before handing the reader a wiki page for what it calls more. That last construction is the honest one: four named formats and an unnamed remainder.
What the list itself describes is a room and a scene model. You grow a premise from one sentence to a paragraph to a summary, create characters, conceive plots, build worlds, track items, and edit and reorganize chapters and scenes, with automatic saves in a documented plain text format. None of that needs a network connection, and none of it is versioned per release in the file.
Four build conventions share one repository root
The root is where the conventions collide. There is a makefile in lowercase rather than a Makefile, a manuskript.spec that is a PyInstaller specification, a main.pyproject beside it, a Dockerfile and a docker-compose.yml, a snap directory for the Snapcraft package the README links, and a package directory whose create_deb.sh the Dockerfile comment names. Six ways to build the same application, in one directory.
Alongside them sit the artefacts a hosted project tends to accumulate. _config.yml is the file name a static site generator uses, which fits a repository that also hosts the project website the README points readers at. TODO.t2t means the to-do list is written in t2t markup rather than plain text. CREDITS and COPYING sit next to CHANGELOG.md, and the GPL text is in COPYING rather than a file called LICENSE.
One file has no counterpart in the header. A .codeclimate.yml config sits at the root, while the three README badges cover the pytest workflow, Snapcraft and Weblate. Nothing in the README tells a contributor which of those four root level configurations is the one a change has to satisfy.
Editorial conclusion
Manuskript fits a writer who wants an offline desktop editor with an open and plain text save format, an outline or index card workflow, and export to HTML, ePub, OpenDocument and DocX, and it costs nothing to try because the licence is GPL version 3 or later. Four things to weigh first. There is no install command in the repository, so you start from the download page and the wiki. The newest tag, 0.17.0, is from 2025-06-30 while the default branch was pushed on 2026-09-01, so the pins in requirements.txt describe untagged work rather than a release. The container is a test runner, root with passwordless sudo, not a way to run the editor. And the feature documentation is a decade old blog archive, so verify the workflow you actually want against the wiki before committing to it.
Frequently asked questions
How do I install Manuskript?
The README points to the download page at theologeek.ch/manuskript/download and sends detailed installation steps to the project wiki. The repository itself contains no install command, and it states only that the program is written in Python3 and PyQt5.
What can Manuskript do for a writer?
It offers an outline mode and index cards, distraction free writing, characters, plots, worlds, tracked items, chapters and scenes, a story line view, fiction and non-fiction templates with writing modes, a spell checker, a markdown highlighter, a frequency analyzer, and automatic saving in an open and plain text format.
What license is Manuskript released under?
The GNU General Public License version 3, or at your option any later version, with the licence text in the COPYING file at the repository root.
Does Manuskript have a Docker setup for running the editor?
The repository ships a Dockerfile on ubuntu:22.04 and a docker-compose.yml, but the single service is a builder: it builds the image, mounts the repository at /workspace, sets QT_QPA_PLATFORM=offscreen and inherits a default command that runs pytest.
Which formats can Manuskript import and export?
The README names HTML, ePub, OpenDocument and DocX, and links a wiki page for the rest of its import and export capabilities. Saves inside the application are automatic and use the open and plain text file format.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/olivierkes-manuskript)