CLI reference
The talewright command validates, lints, debugs and publishes stories. It is an optional tool for
authors who want to make a standalone web or desktop game; you do not need it to share a story zip
with friends. See Getting started
to install it. Until ready-made downloads arrive, use
dotnet run --project tooling/cli/src/Talewright.Cli -- in place of talewright.
| Command | Does |
|---|---|
talewright validate <story> |
Checks a story folder for errors and warnings. |
talewright lint <story> |
Checks compiler diagnostics and configurable Talemark-aware rules. |
talewright debug <story> |
Opens the Author Debugger. |
talewright publish <story> |
Builds a story zip, web game or desktop game. |
talewright --help and talewright <command> --help list every option. Unknown options are rejected.
For the usual one-file web game, start with the sharing guide.
The less common desktop split-build and file-replacement details are covered below.
Options shared by all commands
Put --non-interactive after the command name:
talewright validate path/to/story --non-interactive --format json
talewright publish path/to/story --output story.zip --non-interactive --force
--non-interactive guarantees that the CLI never asks a question, even if a script runner provides a
terminal. It does not imply --force, skip validation, or change diagnostics, logging or JSON.
There is no --ci alias. validate and debug do not ask CLI questions today; they accept the option
for the same contract. debug still opens its graphical workbench; this option does not change
confirmation dialogs inside that window.
validate
talewright validate path/to/story
talewright validate path/to/story --format json --warnings-as-errors
Runs the same compiler as the players on a folder containing story.talewright.yaml and .tale files. Validation
proves the story is well formed; it doesn’t play every possible path.
| Option | Effect |
|---|---|
--warnings-as-errors |
Fail if there are compiler warnings. Warnings pass by default. |
--format json |
Machine-readable output (below). |
Output contract
| Exit code | Meaning |
|---|---|
| 0 | Story accepted (or help shown). |
| 1 | Compiler errors, or warnings when --warnings-as-errors is set. |
| 2 | Invalid command, or the input couldn’t be read. |
Human output prints file:line:column: severity CODE: message per diagnostic, then a final status.
Operational errors go to stderr.
An advisory hosting-size warning also goes to stderr when the source-only estimate
exceeds recorded itch.io guidance. It does not change the exit code, compiler diagnostics,
JSON schema, or --warnings-as-errors policy. Generated runtime and precompressed files
are excluded from this estimate; publish -a --target web measures the final extracted
payload instead. See artwork and hosting size.
--format json writes exactly one JSON object to stdout, with no progress or help text:
{"schemaVersion":1,"success":true,"diagnostics":[]}
- Each diagnostic has
code,severity(errororwarning),message,file(relative to the story),lineandcolumn. Positions are one-based;0means unspecified.--warnings-as-errorsdoesn’t change reported severities. - Operational failures (exit 2) return
success: false, emptydiagnosticsanderror: {"code": "TWCLI001" | "TWCLI002" | "TWCLI003", "message": "..."}:TWCLI001for invocation errors,TWCLI002for input errors, andTWCLI003for invalid lint configuration. - A readable folder with no
story.talewright.yamlis a compiler diagnostic (TW4002), not an operational error.
lint
talewright lint path/to/story
talewright lint path/to/story --format json --warnings-as-errors
talewright lint path/to/story --rule TWL001=error
lint includes compiler diagnostics once, then applies shared Talemark-aware rules. Compiler diagnostics
cannot be suppressed by lint configuration. Lint findings use stable TWL codes and source ranges;
their JSON diagnostics include exclusive endLine and endColumn positions.
The optional talewright.lint.json file lives at the story root, beside the canonical manifest:
{
"rules": {
"TWL001": "warning",
"TWL002": "warning"
},
"suppressions": {
"chapters/draft.tale": ["TWL001"]
}
}
Rule severities are off, warning or error. Both initial rules default to warning; config values
override those defaults. A suppression hides only the listed lint rule in that relative file. Paths use
forward slashes and cannot contain . or .. path segments. Repeat --rule ID=SEVERITY to override
configuration for individual rules; command-line values take precedence. --warnings-as-errors uses the
same policy as validate, and warnings otherwise pass by default.
| Option | Effect |
|---|---|
--rule <ID=SEVERITY> |
Override one rule’s severity (off, warning, or error); may be repeated. |
--warnings-as-errors |
Fail if any compiler or lint warning is reported. |
--format json |
Machine-readable diagnostics, including ranges for lint findings. |
Invalid configuration, including unknown rule IDs or malformed suppression paths, is an explicit
operational error (TWCLI003, exit 2).
debug
talewright debug path/to/story
Opens the native Author Debugger and waits for it to close. For a walkthrough, see Testing your story.
Requirements. A graphical desktop and the OS webview: WebKitGTK 4.1 on Linux
(libwebkit2gtk-4.1-0), WebView2 on Windows, WKWebView on macOS. There’s no browser fallback. Linux x64
is runtime-tested; other platforms need validation. Missing folders, assets or prerequisites, and native
launch failures, exit 2 with TWCLI002. Compiler errors appear in the window so you can fix and save
without closing it.
Workbench behavior. See the testing guide for scene setup and inspection, live edits, jumps and navigation, and source reload/restart. For API and reconstruction invariants, see the runtime documentation.
Distribution. Debugger assets ship in the CLI tool, not in Player packages or published games. See the testing guide for portable scenarios and read-only player-save forks and a hands-on test plan. Author scenarios never become player saves.
publish
talewright publish path/to/story --output story.zip
talewright publish path/to/story -a --target web --output game-web.zip
talewright publish path/to/story -a --target desktop --output game-desktop.zip
talewright publish path/to/story -a --target desktop --runtime win-x64 --output game-windows.zip
talewright publish path/to/story -a --target desktop --split --output desktop-builds/
Validates first, then builds. For a walkthrough, see Sharing your story.
| Option | Effect |
|---|---|
-o, --output <path> |
Output ZIP, or output directory with --split. |
-a, --all-in-one |
Include the player and engine, opening only this story (no samples). Without it, the zip holds story.talewright.yaml, .tale files and safe, supported files from images/ in their folder layout. --a is not accepted. |
--target web |
Default. A static site with a root index.html and relative URLs. |
--target desktop |
A self-contained desktop app. |
--runtime <rid> |
Desktop only: a single platform instead of all six. linux-x64, linux-arm64, win-x64, win-arm64, osx-x64 or osx-arm64. |
--split |
With -a --target desktop, write one self-contained ZIP per supported runtime into the --output directory. Cannot be combined with --runtime; it is not supported for story-only or web output. Filenames are <story-id>-<rid>.zip. |
--force |
Authorize replacement without asking, independently of --non-interactive. |
Advanced: split desktop output
talewright publish path/to/story -a --target desktop --split --output desktop-builds/
This builds each of the six supported RIDs once and writes six ZIPs, such as
desktop-builds/my-story-linux-x64.zip and desktop-builds/my-story-osx-arm64.zip.
Each contains only its runtime-specific self-contained apphost and files, the published story,
the engine license and deployment instructions. The universal start.cmd, start.sh and
start.command launchers are not included. PDB inclusion is unchanged. These are unsigned
standalone ZIPs, not installers or macOS app bundles.
If any output already exists, an interactive terminal lists the existing paths and asks once
whether to replace the set. Declining preserves every existing file and exits 2. In non-interactive
or redirected use, an existing output requires --force; --force authorizes replacing the full
set. The output directory is created as needed. Build and archive all six outputs to temporary files
before replacing any destination, so a build/archive failure leaves the previous set intact.
The six final file replacements are not one filesystem transaction. A machine crash or filesystem failure during that final replacement phase can expose a partial set; ordinary replacement errors trigger best-effort rollback, retaining recovery backups if rollback itself fails. Concurrent publishers targeting the same directory are not locked out. Do not rely on cross-file atomicity or crash durability. Each ZIP independently retains the published-output limits.
Advanced: replacing an existing archive
For a new output path, publication never asks a question. For an existing file, an interactive
terminal shows the full destination path and asks Replace it? [y/N] before compiling or building.
Type y or yes to replace it. No, Enter, end-of-input or any other answer cancels publication,
leaves the existing file alone, and exits 2. The question is written to stderr, not stdout.
With --non-interactive, redirected stdin, stdout or stderr, there is no question and no
confirmation input is read. An existing destination fails with guidance to choose another --output
path or add --force. Piping yes into publish is not approval.
For an intentional scripted update:
talewright publish path/to/story --output story.zip --non-interactive --force
--force also works on its own in an interactive terminal without asking. It never bypasses story
validation or build errors.
Advanced safety detail: overlapping publications. Without force, a file that appears while publishing is not overwritten.
For an approved existing file, publish compares its content fingerprint, size, timestamps and link
target again just before replacement. If it has observably changed or disappeared, publish fails
instead of using the earlier approval. Retry to approve the current file, choose a new path, or
explicitly use --force. Force permits replacement even if the destination appears or changes.
The completed ZIP is staged beside the destination and moved into place; the original is not deleted first. The check and replacement are separate operations, not a filesystem transaction or a lock against other programs. A change in that final gap, or a replacement indistinguishable by the checked content and metadata, may not be detected. Do not run concurrent publishers against the same path when that distinction matters. No universal atomicity or crash-durability guarantee is made across operating systems and filesystems.
Web output runs on any static HTTP(S) host, including beneath a subpath, and can be uploaded to itch.io as an HTML5 game. Players need no backend or .NET.
Desktop output is one universal zip with self-contained .NET for Windows, Linux and macOS on x64 and
ARM64. Extract everything, then run start.cmd (Windows), start.sh (Linux) or start.command (macOS);
the launcher picks the right platform. If extraction dropped execute permissions, use sh start.sh. A
single-runtime zip is smaller; run Talewright.Host.Desktop (.exe on Windows). Photino supplies the
native binaries, so any platform can be built from any machine. Linux x64 is runtime-verified; Windows,
macOS and ARM64 builds are unverified. Players need the OS webview (WebKitGTK 4.1, WebView2 or
WKWebView). Output isn’t an installer, signed, notarized or a macOS .app bundle.
Author requirements. A .NET 10 SDK and package restore access. Installed tools carry their templates, binaries, UI assets and the engine license, so no source checkout is needed. The generated host uses precompiled Player binaries and copies their JS modules as static assets.
All export formats preserve supported image files so the exported story passes the
same compiler lookup as the source. Standalone hosts keep story text and images in
an external story/ directory; universal desktop bundles share one copy across runtimes.
Web hosts fetch individual images when needed. Deploy the complete extracted web
archive, including its story/ directory.
Failures. Compiler or build errors exit 1. Invalid invocation, input, output or missing tools exit 2.
A failed build leaves any existing output unchanged and never writes a partial zip. publish prints
human-readable diagnostics only (--format json is for validate).
Limits.
| Input | Limit |
|---|---|
| Story folders | No authored file-count or byte ceiling; folder depth 64 |
| Symbolic links inside the story | Rejected |
| Published output | No Talewright file-count or byte ceiling |
Staging preserves the root manifest, supporting .talewright.yaml fragments, .tale files and
supported images. Other files are not bundled automatically. Large publications still need
enough disk space and time to build and write. A disk or write failure is reported without
replacing an existing good output with a partial archive.
Hosting-size warnings are advisory and do not prevent publication. Check your chosen host’s limits before uploading. Publication does not relax the separate protections on ZIPs opened by the player: source text is limited to 1,000 files, 5 MiB per file and 50 MiB total expanded text, and browser uploads have a 20 MiB compressed-input limit. A successfully published large story ZIP may exceed those import limits; use an all-in-one publication to distribute a standalone game instead.
These docs are maintained with the code in squid-farm/talewright. Found a problem? Open an issue.