Repository navigation
Commands
ew.py is the script the agent runs for everything that is counting or bookkeeping. It sits in the skill at scripts/ew.py. Every example below was run in order, as one job, against the 0.1.1 release on Linux in a folder named /tmp/demo. On Windows, run python instead of python3; paths print with \.
usage: ew.py [-h] [--version] [--works WORKS]
{intake,check,show,chunk,stats,suggest,render,ledger,apply,export,feedback,author,override,status} ...
--works goes before the command. It names the works store; without it the store comes from $EDITWRIGHT_WORKS, a config file or ~/editwright-works (Works store and privacy). ew.py <command> -h prints each command's options.
A job is named by its folder path, by <work>/<job>, or by the work's slug alone, which picks its newest job. The examples use the slug, the-salt-road.
| Code | Meaning |
|---|---|
| 0 | done |
| 1 | a check failed, or a request was refused (nothing was changed) |
| 2 | usage error, including apply with no --accept and override with a reason under three words |
Messages that start with editwright: go to stderr; everything else goes to stdout. The transcripts below show both together.
Save this as story.md. It is invented, with a typo, a tense slip and a few other things to find.
# The Salt Road
Nell had walked the salt road every morning for nine years. She knew
where the ruts ran deep and where the gulls waited for scraps.
"You're late," said her brother when she reached the gate.
"The tide was high," Nell said. "I had to go round by the old mill."
She saw the cart standing empty by the well. Nobody had unloaded teh barrels, and the mule was gone.
***
By noon the village was talking. Some said the mule had wandered off, as mules do. Others said a stranger
had come in the night and taken it.
Nell walks to the well again and looked at the cart. The axle was cracked. Someone had driven it hard, very hard, and
then left it.
She realized the stranger was her cousin.Snapshots and hashes the source, then writes clean.md, cleanup-log.md, source-text.md, job.json and notes.md in a new job folder. The folder is named for the date, a counter and the level.
$ python3 ew.py --works works intake story.md --work the-salt-road --title "The Salt Road" --author "Test Author" --genre short-story --level full
job: works/the-salt-road/jobs/20261009-1-full
source sha256: cdfeb8dcbc53776821db0c623bac936ce182c087ee6980279f3cdf16854f0aff (snapshot source.md, read-only)
clean.md: 133 words, 9 paragraphs; cleanup rules applied: 2 (cleanup-log.md); word check passed
mode: human-authored; AI-word limit for short-story: 0 words / 0.0%
author preferences: works/authors/test-author.md (none yet)| Option | What it does |
|---|---|
--work |
the work's slug. Without it the slug comes from the file name (story), not from --title. |
--title, --author
|
shown in the outputs; --author names the preferences file |
--genre |
fiction (default), short-story, novel, nonfiction, script, poetry, other; sets the AI-word limit |
--level |
developmental, assessment, line, copy, proof, fact, full (default) |
--style |
style guide for the copy pass (chicago, ap, apa, mla, oxford, house); default chicago, or screenplay for scripts |
--text |
text already extracted from the source (PDF, EPUB, a Google Doc export); the source file is still the one hashed |
--source-ref |
where the source lives when it is not a local file (gdoc:<id>, a URL) |
--quotes |
auto (the source's dominant style, the default), curly, straight, keep
|
--dashes |
auto (double hyphens become em dashes when there are any), em, double, keep
|
--keep-lines |
keep single line breaks (always on for scripts and poetry) |
--no-headings |
do not mark lines like "Chapter 3" as ## headings |
The cleanup also removes invisible characters, normalises Unicode (NFC), turns no-break spaces, thin spaces and tabs into spaces, removes markdown escapes that exporters add, trims indents and trailing spaces, and closes up spaced ellipses. The word count includes headings.
A PDF or EPUB is refused on its own (How it works). With its text extracted and passed in --text, it goes in:
$ touch essay.pdf && cp essay.md extracted.md && python3 ew.py --works works intake essay.pdf --text extracted.md --work essay-pdf --genre nonfiction
job: works/essay-pdf/jobs/20261009-1-full
source sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 (snapshot source.pdf, read-only)
clean.md: 136 words, 4 paragraphs; cleanup rules applied: 0 (cleanup-log.md); word check passed
mode: human-authored; AI-word limit for nonfiction: 50 words / 1.0%(The PDF here is an empty stand-in; the hash is of the empty file.)
Prints clean.md with paragraph numbers. Suggestions use these numbers as anchors.
$ python3 ew.py --works works show the-salt-road
[P1] # The Salt Road
[P2] Nell had walked the salt road every morning for nine years. She knew where the ruts ran deep and where the gulls waited for scraps.
[P3] "You're late," said her brother when she reached the gate.
[P4] "The tide was high," Nell said. "I had to go round by the old mill."
[P5] She saw the cart standing empty by the well. Nobody had unloaded teh barrels, and the mule was gone.
[P6] * * *
[P7] By noon the village was talking. Some said the mule had wandered off, as mules do. Others said a stranger had come in the night and taken it.
[P8] Nell walks to the well again and looked at the cart. The axle was cracked. Someone had driven it hard, very hard, and then left it.
[P9] She realized the stranger was her cousin.--para N or --para N-M prints a range, --file prints another text file in the job folder, and --max-chars (default 25000) cuts long output.
$ python3 ew.py --works works show the-salt-road --para 7-8
[P7] By noon the village was talking. Some said the mule had wandered off, as mules do. Others said a stranger had come in the night and taken it.
[P8] Nell walks to the well again and looked at the cart. The axle was cracked. Someone had driven it hard, very hard, and then left it.Counts, not verdicts: sentence lengths, close repeats, -ly adverbs, filter and crutch words, dialogue share and tags. On a job it also saves stats.json. --json prints everything as JSON.
$ python3 ew.py --works works stats the-salt-road
words 130, paragraphs 9, sentences 14
sentence length: mean 9.3, median 10.0, sd 2.6, range 4-14; histogram 1-5:1, 6-10:8, 11-20:5, 21-30:0, 31-40:0, 41+:0
longest: P2 (14 w) She knew where the ruts ran deep and whe...; P7 (12 w) Others said a stranger had come in the n...; P2 (11 w) Nell had walked the salt road every morn...
dialogue share: 10.4% of characters
-ly adverbs: 0.0 per 1000 ()
filter words: 30.8 per 1000 (knew 1, saw 1, looked 1, realized 1) in P2,5,8,9
crutch words: very 1, then 1
close repeats (60-word window): nell x2 P4,8; mule x1 P7; well x1 P8; cart x1 P8; hard x1 P8; stranger x1 P9
top words: nell 3, cart 2, well 2, mule 2, stranger 2, hard 2, walked 1, salt 1, road 1, every 1, morning 1, nine 1
was/were + -ing: 1; began/started to: 0; dialogue tags: {'said': 2}; same first word twice running: none
(counts only; whether any of it is a problem is the editor's call, in context)stats counts 130 words where intake counted 133: it leaves out headings and scene breaks. It also takes a plain text file (ew.py stats story.md), which it reads as it is, without cleanup, so hard-wrapped lines show up in the "longest" quotes.
Splits clean.md into chunks of whole paragraphs for long manuscripts, starting a new chunk at a heading once the current one has --min-words (default 1500), and never going over --max-words (default 6000). Small limits here, to show the output on a short story:
$ python3 ew.py --works works chunk the-salt-road --max-words 60 --min-words 20
3 chunks in /tmp/demo/works/the-salt-road/jobs/20261009-1-full/chunks (max 60 words each); index chunks/INDEX.md$ cat works/the-salt-road/jobs/*/chunks/INDEX.md
# Chunks
Each chunk holds whole paragraphs; paragraph numbers match clean.md. Summarise each chunk into the story bible before moving on.
| Chunk | Paragraphs | Words | First line |
|---|---|---|---|
| [001](001.md) | P1-P4 | 53 | # The Salt Road |
| [002](002.md) | P5-P7 | 47 | She saw the cart standing empty by the well. Nobody had unlo |
| [003](003.md) | P8-P9 | 33 | Nell walks to the well again and looked at the cart. The axl |Validates a JSON list of suggestions and adds them to the job. Every quote must be found verbatim in clean.md. Items that pass are saved; items that fail are listed by number, and the command exits 1. Fix them and run suggest again with the same file: items already saved are skipped. In 0.1.0 one bad item meant nothing was saved. The format is on Suggestion format, with the six suggestions used here.
$ python3 ew.py --works works suggest the-salt-road bad.json
added 0 suggestion(s), 0 in total; 0 carry AI-written words
refused 1 suggestion(s) (the others were saved); fix these and run suggest again with the same file:
- item 1: quote not found in the text: 'She saw the cart standing empty by the barn.'
quotes must match `ew.py show` exactly, curly quotes and apostrophes included
$ echo $?
1$ python3 ew.py --works works suggest the-salt-road suggestions.json
added 6 suggestion(s), 6 in total; 1 carry AI-written words (S-004)
note: human-authored mode with a 0-word limit; apply will refuse these unless the author supplies the words (--author-text) or the job is overriddenIt writes suggestions.json, suggestions.md, review.md, provenance-ledger.md and provenance.json. A second suggest adds to the list; --replace-all drops the existing suggestions first. What the files look like is on Delivery formats.
Rewrites suggestions.md, review.md and the ledger from suggestions.json.
$ python3 ew.py --works works render the-salt-road
wrote suggestions.md, review.md, provenance-ledger.md and provenance.json in /tmp/demo/works/the-salt-road/jobs/20261009-1-fullPrints the ledger's totals. With --accept, it checks a set of IDs against the limit without applying anything, and exits 1 when the set is over.
$ python3 ew.py --works works ledger the-salt-road
piece 133 words; all proposed changes: 3 AI-written words (2.26%); applied: 0 (0.00%)
ledger: /tmp/demo/works/the-salt-road/jobs/20261009-1-full/provenance-ledger.md$ python3 ew.py --works works ledger the-salt-road --accept S-004
accepting S-004 would bring in 3 AI-written words (2.26%): over the limit (0 words / 0.0% for short-story in human-authored mode)
$ echo $?
1Applies the accepted IDs to a new file beside clean.md and writes its changelog. Every refusal leaves everything as it was:
$ python3 ew.py --works works apply the-salt-road --accept S-009
refused: S-009 not in suggestions.json; nothing applied
$ echo $?
1$ python3 ew.py --works works apply the-salt-road --accept S-005
refused; nothing applied:
- S-005 is a query with no proposed change; the author supplies the words (--author-text S-005="...")
$ echo $?
1$ python3 ew.py --works works apply the-salt-road --accept S-002,S-006
refused: S-006 and S-002 change overlapping text; accept one of them
$ echo $?
1Here the author accepts the typo fix, the cut and the tense fix, and writes their own sentence for the query S-005:
$ python3 ew.py --works works apply the-salt-road --accept S-001,S-002,S-003,S-005 --author-text S-005="She knew then that the stranger was her cousin."
applied 4 change(s) to a copy: /tmp/demo/works/the-salt-road/jobs/20261009-1-full/edited.md (changelog edited-changelog.md)
AI-written words in this file: 0 (0.00%); within the limit (0 words / 0.0%)
the source was not touched; run `ew.py check` to confirm$ cat works/the-salt-road/jobs/*/edited-changelog.md
# Changelog: edited.md
From clean.md (sha256 4e90b819b5cbdf42). Only the IDs the author accepted were applied. AI-written words: within the limit (0 words / 0.0%).
| ID | Para | Before | After | Words | Who wrote the new words |
|---|---|---|---|---|---|
| S-001 | P5 | teh | the | corrected teh>the | nobody: author's own words |
| S-003 | P8 | walks | walked | corrected walks>walked | nobody: author's own words |
| S-002 | P8 | very hard, | (cut) | a cut, no new words; removed very, hard | nobody: author's own words |
| S-005 | P9 | She realized the stranger was her cousin. | She knew then that the stranger was her cousin. | the author wrote 3 new word(s) | author |In the S-005 row, the "Words" column counts the words the author typed. (In 0.1.0 it called them "AI-written", though the last column and the totals said author.) The edited file:
$ cat works/the-salt-road/jobs/*/edited.md
# The Salt Road
Nell had walked the salt road every morning for nine years. She knew where the ruts ran deep and where the gulls waited for scraps.
"You're late," said her brother when she reached the gate.
"The tide was high," Nell said. "I had to go round by the old mill."
She saw the cart standing empty by the well. Nobody had unloaded the barrels, and the mule was gone.
* * *
By noon the village was talking. Some said the mule had wandered off, as mules do. Others said a stranger had come in the night and taken it.
Nell walked to the well again and looked at the cart. The axle was cracked. Someone had driven it hard, and then left it.
She knew then that the stranger was her cousin.Each apply builds a fresh file from clean.md; it does not build on the last edited file. A second apply writes edited-2.md holding only that call's IDs. So name every ID you accept in one call. --author-file takes a JSON object of { "S-005": "text" } in place of repeated --author-text, and --docx also writes the edited text as a .docx:
$ python3 ew.py --works works apply the-salt-road --accept S-001 --docx && ls works/the-salt-road/jobs/*/ | grep edited-3
applied 1 change(s) to a copy: /tmp/demo/works/the-salt-road/jobs/20261009-1-full/edited-3.md (changelog edited-3-changelog.md)
AI-written words in this file: 0 (0.00%); within the limit (0 words / 0.0%)
the source was not touched; run `ew.py check` to confirm
edited-3-changelog.md
edited-3.docx
edited-3.mdWrites review.docx: clean.md with each proposed change as a Word tracked change and each suggestion as a comment.
$ python3 ew.py --works works export the-salt-road
wrote /tmp/demo/works/the-salt-road/jobs/20261009-1-full/review.docx: 4 tracked suggestion(s) and 5 comment(s) on a copy of clean.md
left out (they overlap an earlier change; they are in suggestions.md): S-002
open it in Word, or upload it to Google Drive and open with Google Docs; accepting there is the author's act--out names a different file in the job folder. More on Delivery formats.
Records which IDs the author took and turned down. With an author set at intake, it updates their preferences, tallied by category and level.
$ python3 ew.py --works works feedback the-salt-road --accepted S-001,S-002,S-003,S-005 --rejected S-004,S-006 --note "Liked the cuts; leave the asides alone."
recorded 4 accepted, 2 rejected; preferences: works/authors/test-author.json
by category: typo 1/1 taken; repetition 1/1 taken; tense 1/1 taken; stakes 1/1 taken; rhythm 0/1 taken; punctuation 0/1 takenThe --note goes into the job's notes.md. The agent adds a line to the author's .md file for any taste they stated.
Prints an author's preferences: the tallies and the notes file.
$ python3 ew.py --works works author "Test Author"
by category: typo 1/1 taken; repetition 1/1 taken; tense 1/1 taken; stakes 1/1 taken; rhythm 0/1 taken; punctuation 0/1 taken
# Author preferences: Test Author
Read before every pass. Tallies in test-author.json; taste notes below, one line each with the date and the job.
## Takes
## Turns down
## NotesTurns human-authored mode off for one job, quoting the user. See How it works for the refusal of a short reason. With the mode off, the change that was refused before goes in, and the ledger still counts it:
$ python3 ew.py --works works apply the-salt-road --accept S-004
applied 1 change(s) to a copy: /tmp/demo/works/the-salt-road/jobs/20261009-1-full/edited-2.md (changelog edited-2-changelog.md)
AI-written words in this file: 3 (2.26%); allowed (human-authored mode overridden for this job: rewrite it freely, this one is a practice piece)
the source was not touched; run `ew.py check` to confirm$ python3 ew.py --works works override the-salt-road --off
human-authored mode back on for this jobA summary of a job.
$ python3 ew.py --works works status the-salt-road
job 20261009-1-full: The Salt Road (short-story, full), 133 words, mode human-authored
suggestions: 6 (developmental 1, line 2, copy 2, proof 1); applied 1; rejected 2
files: clean.md, cleanup-log.md, suggestions.json, suggestions.md, review.md, provenance-ledger.md, edited.md
folder: /tmp/demo/works/the-salt-road/jobs/20261009-1-full"applied 1" describes the newest edited file (edited-2.md, from the override example), and the files line lists edited.md only, not edited-2.md.
Confirms that the source and its snapshot are unchanged and that clean.md has the same words as the source. It exits 1 when anything changed (an example is on How it works).
$ python3 ew.py --works works check the-salt-road
OK source /tmp/demo/story.md: unchanged
OK snapshot source.md: matches
OK clean.md: same words as the source
check: PASSED--against compares a fresh export of a remote source, such as a Google Doc, with the snapshot. Same words with different bytes still pass. Here the export has Windows line endings:
$ sed 's/$/\r/' story.md > export.txt && python3 ew.py --works works check the-salt-road --against export.txt
OK source /tmp/demo/story.md: unchanged
OK snapshot source.md: matches
OK export.txt: same words as the snapshot (bytes differ)
OK clean.md: same words as the source
check: PASSEDEach check also writes its result line to last-check.txt at the root of the works store.
$ python3 ew.py --works works show no-such-work
editwright: no job found for 'no-such-work' (looked in no-such-work, works/no-such-work)
$ echo $?
1This wiki describes editwright 0.1.1 (tag v0.1.1, commit 8d9a0d5) and was last updated on 2026-10-09. The plugin is MIT licensed. Report problems in the issues.