Repository navigation
Commands
wikiwright.py is the helper the skill runs. It lives at skills/wikiwright/scripts/wikiwright.py, is one file, needs Python 3.9 or later and imports only the standard library. Every transcript on this page ran from a fresh clone of master (a809eff, tag v0.9.0) on 2026-10-03, on Windows 11 with Python 3.14.6 and again with 3.9.25. Paths inside the scratch clone print as <clone>. The rules behind check, outputs, snippets and live are on What the helper checks.
Run with no subcommand, it prints its usage line:
$ python skills/wikiwright/scripts/wikiwright.py
usage: wikiwright.py [-h] [--version]
{preflight,check,live,outputs,snippets,unbs,diffout,cachecheck,releasecheck,registry,scaffold} ...
wikiwright.py: error: the following arguments are required: cmd
$ echo $?
2Python 3.9's argparse wraps the ... onto a third line and says "optional arguments" where 3.14 says "options"; nothing else differed. --version prints 0.9.0, and --help after any subcommand prints its options.
| Subcommand | What it does | Network |
|---|---|---|
| preflight | wiki feature, wiki repository, placeholder, clone | github.com through gh and git
|
| check | lint a wiki working copy | none |
| outputs | every output a page shows is in the saved verification output | none |
| snippets | every code block a page shows is in the verification program | none |
| live | the published pages answer and render | github.com (or the other host) |
| unbs | replace backslash placeholders with backslashes | none |
| diffout | compare two runs of a verification script, section by section | none |
| scaffold | write the verification script, filled in and cut to the sections needed | none |
| registry | a compact survey of an npm or NuGet package | npmjs.org or nuget.org |
| cachecheck | the installed plugin against the source, by SHA-256 | none |
| releasecheck | version fields, changelog entry and a test run before tagging | none |
| Code | Meaning |
|---|---|
| 0 | clean |
| 1 | findings: errors from a check, differences from diffout, a failed page from live
|
| 2 | a usage or environment error: a missing folder, a bad argument, a file that would be overwritten |
The helper prints its findings and its errors on stdout. Only argparse's usage errors (an unknown subcommand or option, a missing argument) go to stderr. An unknown subcommand:
$ python skills/wikiwright/scripts/wikiwright.py publish
usage: wikiwright.py [-h] [--version]
{preflight,check,live,outputs,snippets,unbs,diffout,cachecheck,releasecheck,registry,scaffold} ...
wikiwright.py: error: argument cmd: invalid choice: 'publish' (choose from 'preflight', 'check', 'live', 'outputs', 'snippets', 'unbs', 'diffout', 'cachecheck', 'releasecheck', 'registry', 'scaffold')
$ echo $?
2With stderr thrown away, the usage error disappears and the check error does not:
$ python skills/wikiwright/scripts/wikiwright.py publish 2>/dev/null
$ echo $?
2
$ python skills/wikiwright/scripts/wikiwright.py check nowhere 2>/dev/null
error: nowhere is not a directory
$ echo $?
2preflight REPO [--enable] [--wait SEC] [--clone DIR] [--kind KIND] [--seed]
REPO is OWNER/REPO, a remote URL or a clone with an origin. It needs gh logged in.
| Option | What it does |
|---|---|
--enable |
switches the wiki feature on when it is off (gh repo edit --enable-wiki) |
--wait SEC |
keeps re-checking git ls-remote for this many seconds (60 after --enable) |
--clone DIR |
clones the wiki repository into DIR (or fetches, when DIR already holds it) and says whether it is a placeholder |
--kind KIND |
github, gitea, forgejo, gitlab or azure, when neither the host name nor its API tells |
--seed |
Gitea and Forgejo only: create the wiki with a first page through the API, with a token in WIKIWRIGHT_TOKEN
|
It ends with a STATE: line. On everwrite's wiki, which has pages:
$ python skills/wikiwright/scripts/wikiwright.py preflight m4bwav/everwrite
repo: m4bwav/everwrite visibility: PUBLIC archived: False wiki feature: True
ls-remote: a86ebc1 HEAD
ls-remote: a86ebc1 refs/heads/master
branch: master (push here; only the default branch goes live)
STATE: exists (branch master; clone it with --clone DIR to see whether it is a placeholder)
$ python skills/wikiwright/scripts/wikiwright.py preflight m4bwav/everwrite --clone everwrite.wiki
repo: m4bwav/everwrite visibility: PUBLIC archived: False wiki feature: True
ls-remote: a86ebc1 HEAD
ls-remote: a86ebc1 refs/heads/master
branch: master (push here; only the default branch goes live)
clone: everwrite.wiki
commits: 2 files: 11 (Commands.md, Development.md, FAQ.md, Getting-Started.md, Home.md, How-The-Skill-Works.md, Recipes.md, Versions-and-Upgrading.md, What-The-Checker-Flags.md, _Footer.md, _Sidebar.md)
STATE: has-pages (existing content: read every page first; update, never overwrite)The states on GitHub:
| State | Exit | Meaning |
|---|---|---|
exists |
0 | the wiki repository is there; add --clone to see what it holds |
placeholder |
0 | one commit holding only a short Home.md: write the pages in the clone and push, no force needed |
has-pages |
0 | someone wrote pages: read them all and update in place |
feature-off |
1 | the wiki feature is off; run again with --enable
|
no-wiki-repo |
1 | no repository yet; someone must save a first page at https://github.com/OWNER/REPO/wiki/_new (no API can create it) |
archived |
1 | an archived repository's wiki cannot be edited |
other-host |
2 | the remote is not a host the helper knows; pass --kind
|
placeholder was the state of this wiki's own repository before these pages were pushed. feature-off, no-wiki-repo and archived are read from the source and were not run for this wiki. Something that is not a repository at all:
$ python skills/wikiwright/scripts/wikiwright.py preflight nowhere
error: nowhere is not OWNER/REPO, a remote URL or a clone with an origin
$ echo $?
2check DIR [--version X] [--partial] [--host KIND]
| Option | What it does |
|---|---|
--version X |
the footer must name X and a date; Home.md should mention X (a warning) |
--partial |
a draft of some pages: no sidebar or footer required |
--host KIND |
the host's rules for navigation files, wikilinks, anchors and file names (default github) |
A draft with most of the common mistakes. draft-wiki/Home.md:
# Home
Widget 1.2.0 sorts words. Read [[Getting Started]] first.
See [the API](API-Reference.md), [Recipes](Recipes) and [the options](Getting-Started#options).
## Sorting Words QuicklyGetting-Started.md is one line, "Install it with pip.", saved with a CRLF ending; _Sidebar.md links only Home, and _Footer.md says "This wiki describes widget 1.2.0." with no date.
$ python skills/wikiwright/scripts/wikiwright.py check draft-wiki --version 1.2.0
Getting-Started.md:0: error: 1 carriage returns (wiki pages are LF only)
Home.md:1: error: the first heading repeats the page title the host prints from the file name; start with the first paragraph: Home
Home.md:3: error: wikilink; use [Text](Page-Name)
Home.md:5: error: link to API-Reference.md: drop the .md (wiki links are page names)
Home.md:5: error: link to missing page API-Reference
Home.md:5: error: link to missing page Recipes
Home.md:5: error: no heading for #options on Getting-Started
_Footer.md:1: error: names no date (YYYY-MM-DD)
Home.md:7: warn: heading looks like Title Case; use sentence case: Sorting Words Quickly
_Sidebar.md:0: warn: does not link Getting-Started
check: 2 pages, 8 errors, 2 warnings
$ echo $?
1The page count leaves out the sidebar and footer. A draft of two pages with no sidebar or footer passes only with --partial:
$ python skills/wikiwright/scripts/wikiwright.py check draft
_Sidebar.md:0: error: missing (a draft of a few pages: --partial)
_Footer.md:0: error: missing (a draft of a few pages: --partial)
check: 2 pages, 2 errors, 0 warnings
$ python skills/wikiwright/scripts/wikiwright.py check draft --partial
check: 2 pages, 0 errors, 0 warnings
$ echo $?
0A version the footer does not name is an error; on Home it is a warning:
$ python skills/wikiwright/scripts/wikiwright.py check tests/fixtures/good-wiki --version 1.3.0
_Footer.md:1: error: does not name version 1.3.0
Home.md:0: warn: does not mention version 1.3.0
check: 3 pages, 1 errors, 1 warnings
$ echo $?
1The same sample wiki under GitLab's rules, then Forgejo's:
$ python skills/wikiwright/scripts/wikiwright.py check tests/fixtures/good-wiki --host gitlab
_sidebar.md:0: error: missing (a draft of a few pages: --partial)
_Sidebar.md:0: error: GitLab reads _sidebar.md (lower case) and ignores this file
_Footer.md:0: warn: gitlab shows no footer; put the version line on Home
check: 3 pages, 2 errors, 1 warnings
$ python skills/wikiwright/scripts/wikiwright.py check tests/fixtures/good-wiki --host forgejo
check: 3 pages, 0 errors, 0 warningsA folder that does not exist is exit 2:
$ python skills/wikiwright/scripts/wikiwright.py check nowhere
error: nowhere is not a directory
$ echo $?
2outputs DIR VERIFY... [--address URL] [--node N]
| Option | What it does |
|---|---|
VERIFY... |
one or more saved outputs of the verification script (the main run and the oldest Node's, for example) |
--address URL |
the address pages show in place of the fixture's http://127.0.0.1:<port> (default https://example.com; '' for none) |
--node N |
the Node major every output ran on, instead of reading each output's Node vN line |
With the page pages/Home.md below and a saved output whose ## home-sort section prints ['apple', 'fig', 'pear']:
Widget sorts words.
```python
words = ["pear", "fig", "apple"]
print(sorted(words))
```
It prints:
```text
['apple', 'fig', 'pear']
```
Install it first.
```sh
pip install widget
```$ python skills/wikiwright/scripts/wikiwright.py outputs pages wiki-verify.out.txt
outputs: 1 pages, 1 outputs checked, 0 missing, 0 skipped
$ echo $?
0The run where the page's output was edited by hand is on Home. A page with a code block and nothing outputs reads as output fails:
$ python skills/wikiwright/scripts/wikiwright.py outputs pages wiki-verify.out.txt
outputs: 1 pages, 0 outputs checked, 0 missing, 0 skipped
error: 1 code blocks and no output recognised; tag each output fence ```text, or mark a page that shows none with <!-- outputs: skip (reason) -->
$ echo $?
1--address: a page showing https://example.com/ answered 200 against a saved output that printed http://127.0.0.1:4321/ answered 200. The default reads the fixture address as https://example.com; --address '' compares as written:
$ python skills/wikiwright/scripts/wikiwright.py outputs pages wiki-verify.out.txt
outputs: 1 pages, 1 outputs checked, 0 missing, 0 skipped
$ python skills/wikiwright/scripts/wikiwright.py outputs pages wiki-verify.out.txt --address ''
Fetch.md:3: error: output block not in the verify output: https://example.com/ answered 200
outputs: 1 pages, 1 outputs checked, 1 missing, 0 skipped
$ echo $?
1snippets DIR PROGRAM...
The same pages/Home.md, with a verification program wiki-verify.py that holds its code:
# wiki-verify.py: runs every example on the pages
words = ["pear", "fig", "apple"]
print(sorted(words))$ python skills/wikiwright/scripts/wikiwright.py snippets pages wiki-verify.py
Home.md:16: command, not checked: pip install widget
snippets: 1 pages, 1 blocks checked, 0 missing, 0 skipped, 1 commands
$ echo $?
0The pip install block only runs a command, so it is counted and not checked. A page whose code differs from the program is on Home.
live REPO DIR [--kind KIND] [--no-anchors]
DIR is the wiki working copy, which gives the page list and the sidebar and footer text to look for. Run after a push. On everwrite's wiki, cloned by the preflight above:
$ python skills/wikiwright/scripts/wikiwright.py live m4bwav/everwrite everwrite.wiki
ok 200 Commands
ok 200 Development
ok 200 FAQ
ok 200 Getting-Started
ok 301 Home -> https://github.com/m4bwav/everwrite/wiki
ok 200 How-The-Skill-Works
ok 200 Recipes
ok 200 Versions-and-Upgrading
ok 200 What-The-Checker-Flags
ok 200 wiki root
ok footer renders (1 of 1 probes found)
ok sidebar renders (12 of 12 probes found)
ok anchors: 11 links to 7 pages, 0 broken, 0 extra fetches
live: 9 pages, 0 failures
$ echo $?
0GitHub can take a few seconds to serve a page after a push; run it again once before calling a failure.
unbs FILE...
Replaces every backslash placeholder (<BS>) with a backslash, in place, and says how many it replaced. With pages/Regex.md holding "Match digits with" and the placeholder before d+:
$ python skills/wikiwright/scripts/wikiwright.py unbs pages/Regex.md
pages/Regex.md: 1 replaced
unbs: 1 placeholders replaced in 1 files
$ cat pages/Regex.md
Match digits with `\d+`.It replaces every occurrence, including one that names the placeholder in prose, so never run it on a file that explains the placeholder.
diffout OLD NEW [--skip REGEX] [--mask REGEX] [--context N] [--save FILE] [--keep-paths] [--keep-node]
Splits two outputs of a verification script at their ## label lines and compares them section by section. Line endings, trailing spaces and local ports (127.0.0.1:<digits>, localhost:<digits>) are normalised first, so a new fixture port is not a change.
| Option | What it does |
|---|---|
--skip REGEX |
leaves out sections whose label matches (repeatable) |
--mask REGEX |
replaces matching text with <masked> (repeatable) |
--context N |
unchanged lines around each change (default 1) |
--save FILE |
writes the normalised new output, with the new output's folder, the temp folder and the home folder as <scratch>, <temp> and <home>
|
--keep-paths |
leaves local paths as they are |
--keep-node |
compares shell node: vN lines too; by default they are counted per output and masked |
With an old output whose sections are installed (Python 3.9.25), home-sort and server (listening on port 50123), and a new one with Python 3.14.6, port 61007 and an extra home-reverse section:
$ python skills/wikiwright/scripts/wikiwright.py diffout old.txt new.txt
*** changed: ## installed
@@ -1 +1 @@
-Python 3.9.25
+Python 3.14.6
+++ added: ## home-reverse
+ ['pear', 'fig', 'apple']
diffout: 4 sections, 2 same, 1 changed, 1 added, 0 removed, 0 skipped
$ echo $?
1
$ python skills/wikiwright/scripts/wikiwright.py diffout old.txt new.txt --skip installed --save saved.txt
+++ added: ## home-reverse
+ ['pear', 'fig', 'apple']
saved the normalised new output to saved.txt
diffout: 4 sections, 2 same, 0 changed, 1 added, 0 removed, 1 skipped
$ cat saved.txt
## installed
Python 3.14.6
## home-sort
['apple', 'fig', 'pear']
## server
listening on 127.0.0.1:<port>
## home-reverse
['pear', 'fig', 'apple']Exit 1 means the outputs differ, not that something broke. An output whose shell cases ran on two Node versions is also exit 1, with a warning.
scaffold npm PACKAGE VERSION [--bin] [--requests] [--by-host] [--files] [--golden OLD_VERSION] [-o FILE] [--force]
scaffold nuget ID VERSION --namespace NS --type T [--children TFMS] [--requests] [--fsharp] [--tool ID:COMMAND] [-o FILE] [--force]
Writes the verification script from the skill's template with the package and version filled in and only the sections the package needs. It reads no registry: it does not check that the package or version exists.
| Option | Template | What it keeps |
|---|---|---|
--bin |
npm |
cli(), term() and the help case, for a package with a bin |
--requests |
both | npm: the local fixture server; NuGet: the stand-in proxy and its gate (implies --children) |
--by-host |
npm | serving the fixture under real host names (implies --requests); copies host-fixture.mjs beside the script |
--files |
npm | the files-on-disk section; copies file-tree.mjs
|
--golden OLD_VERSION |
npm | the replay of a golden capture of an old version |
--namespace NS, --type T
|
NuGet | required: the namespace the examples use and a public type of the package |
--children TFMS |
NuGet | each page example as a whole program on net10.0 and these frameworks (net48,net8.0) |
--fsharp |
NuGet | F# through dotnet fsi
|
--tool ID:COMMAND |
NuGet | installing a dotnet tool into scratch and its transcripts |
-o FILE |
both | the file to write (default ./wiki-verify.mjs or ./wiki-verify.cs) |
--force |
both | overwrite the script and kits when they exist |
For get-title-at-url, an npm package with a bin:
$ python skills/wikiwright/scripts/wikiwright.py scaffold npm get-title-at-url 3.0.0 --bin -o verify/wiki-verify.mjs
wrote <clone>\verify\wiki-verify.mjs: 14,608 bytes (the template is 25,516)
kept: core, bin; dropped: requests, by-host, files, golden, example
package.json: wrote {"private": true}
next:
- npm install --prefix <clone>\verify get-title-at-url@3.0.0, and typescript for TypeScript snippets (typescript6@npm:typescript@6 for a second compiler)
- under '// ----- the cases': one snippet() per code block on the pages, labelled by page
- run: node wiki-verify.mjs > wiki-verify.out.txt, then again with OLDEST_NODE=<the oldest major in engines>
$ ls verify
package.json
wiki-verify.mjs
$ python skills/wikiwright/scripts/wikiwright.py scaffold npm get-title-at-url 3.0.0 --bin -o verify/wiki-verify.mjs
error: <clone>\verify\wiki-verify.mjs exists (pass --force to overwrite it)
$ echo $?
2The paths print with backslashes because the run was on Windows. For RandomNameGeneratorLibrary, a NuGet library, with whole-program examples on .NET Framework 4.8 as well:
$ python skills/wikiwright/scripts/wikiwright.py scaffold nuget RandomNameGeneratorLibrary 2.3.0 --namespace RandomNameGeneratorLibrary --type PersonNameGenerator --children net48 -o verify/wiki-verify.cs
wrote <clone>\verify\wiki-verify.cs: 22,827 bytes (the template is 42,477)
kept: core, children; dropped: requests, fsharp, tool
children: net10.0, net48 (net10.0 always; add a config per end of a dependency range, new("net48", ["Autofac@9.3.4"]))
next:
- run from a scratch folder outside any project, with TEMP and TMP set to it (C:/ paths from Git Bash): dotnet build wiki-verify.cs && dotnet run --no-build wiki-verify.cs > wiki-verify.out.txt
- `dependencies`: the assembly names of the dependencies whose versions the pages name
- `examples`: one whole program per C# block on the pages, as the page shows it, labelled by pageA flag for the other template is refused:
$ python skills/wikiwright/scripts/wikiwright.py scaffold npm get-title-at-url 3.0.0 --fsharp
error: --fsharp: not for scaffold npm
$ echo $?
2The scripts it writes were not run for this wiki.
registry NAME [--nuget | --npm] [--version X] [--limit N] [--no-nupkg] [--json]
One fact per line about a published package, in place of the raw registry JSON. Without a flag, a name with capitals is read from NuGet, a scoped name from npm, and a lower-case name from npm first, then NuGet.
| Option | What it does |
|---|---|
--nuget, --npm
|
read only that registry |
--version X |
describe that version's package instead of the latest |
--limit N |
show the newest N versions (default 40; 0 shows all) |
--no-nupkg |
NuGet: skip downloading the .nupkg, so no file listing |
--json |
the full survey as JSON |
Download counts change daily; these are from the run on 2026-10-03 (the helper stamps the time in UTC):
$ python skills/wikiwright/scripts/wikiwright.py registry get-title-at-url --limit 3
npmjs.org get-title-at-url (a lower-case name is read from npm first; --nuget reads nuget.org)
license: MIT; maintainers: markrogers
repository: git+https://github.com/m4bwav/get-title-at-url.git
homepage: https://github.com/m4bwav/get-title-at-url#readme
description: Get the title of the web page at a URL. Zero dependencies, TypeScript, ESM and CommonJS, Node 20+.
dist-tags: latest 3.0.0
versions: 33 (0 deprecated), latest 3.0.0
... 30 older version(s) not shown (--limit 0 shows all); the first, 1.0.0, on 2016-05-06
2022-11-29 2.0.0
2026-09-25 3.0.0-beta.1, 3.0.0
3.0.0 engines: node >=20
3.0.0 type module; main ./dist/index.cjs; types ./dist/index.d.cts; exports ., ./package.json; bin get-title-at-url
3.0.0 dependencies: none
3.0.0 unpacked 140555 bytes, 11 files; provenance yes
downloads last week: 544 (2026-09-25 to 2026-10-01)
downloads last week by version: 3.0.0 193, 2.0.0 115, 1.1.8 26, 3.0.0-beta.1 25, 1.1.6 8, 1.1.5 3, 1.1.7 2, 1.1.0 1, and 5 more version(s) with 5; 20 version(s) none
read 2026-10-04 03:24 UTC: 3 requests, 17352 bytes on the wire (76825 decoded)$ python skills/wikiwright/scripts/wikiwright.py registry RandomNameGeneratorLibrary --limit 3 --no-nupkg
nuget.org RandomNameGeneratorLibrary (Random Name Generator)
authors: Mark Rogers; owners: rogersm0; license: MIT
project: https://github.com/m4bwav/DotNetRandomNameGenerator
description: Generates random people and place names drawn from freely available US census data, and the names of real stars from the IAU star-name list and the Bright Star, Henry Draper and Hipparcos catalogues....
tags: random, name, generator, census, person, place, star, astronomy, fake, test-data
versions: 16 (4 unlisted), latest 2.3.0; total downloads 3894453
... 13 older version(s) not shown (--limit 0 shows all); the first, 1.0.0, on an unknown date
2.2.0 2026-09-27 listed 97 downloads
2.3.0-beta.1 2026-09-28 listed 40 downloads
2.3.0 2026-09-28 listed 120 downloads
2.3.0 dependencies net10.0: none
2.3.0 dependencies .NETStandard2.0: none
read 2026-10-04 03:25 UTC: 2 requests, 3743 bytes on the wire (36660 decoded)Without --no-nupkg it also downloads the package and lists its lib/ folders, README, icon, size and repository commit. --json was not run for this wiki.
cachecheck [--source DIR] [--cache DIR]
Compares every tracked file under skills/ and .claude-plugin/ in the source with the installed copy, by SHA-256. The source defaults to the repository holding the script, the cache to wikiwright's install path in ~/.claude/plugins/installed_plugins.json. A copy with one changed file:
$ mkdir cache && cp -r .claude-plugin skills cache/
$ echo changed >> cache/skills/wikiwright/SKILL.md
$ python skills/wikiwright/scripts/wikiwright.py cachecheck --cache cache
source: <clone>
cache: cache
differs: skills/wikiwright/SKILL.md
cachecheck: 32 files, 31 equal, 1 differ, 0 missing, 0 only in the cache
reinstall: claude plugin uninstall wikiwright@<marketplace>, then claude plugin install wikiwright@<marketplace> (update keeps a stale copy while the version is unchanged, L-012)
$ echo $?
1releasecheck X.Y.Z [--root DIR]
For maintainers, before tagging a release: the version in .claude-plugin/plugin.json, evergreen.json, VERSION in wikiwright.py and metadata.version in SKILL.md, a CHANGELOG entry naming the version, a TESTS.md run that the last tag did not have, and a free tag. On master today, where 0.9.0 is already tagged:
$ python skills/wikiwright/scripts/wikiwright.py releasecheck 0.9.0
ok .claude-plugin/plugin.json version: 0.9.0
ok evergreen.json version: 0.9.0
ok wikiwright.py VERSION: 0.9.0
ok SKILL.md metadata.version: 0.9.0
ok CHANGELOG entry naming 0.9.0: C-20261001-7 · 2026-10-01 · Release 0.9.0: the ninth and tenth runs, referen
FAIL TESTS run since v0.9.0: none
FAIL tag v0.9.0 exists already
releasecheck 0.9.0: 2 problem(s)
$ echo $?
1
$ python skills/wikiwright/scripts/wikiwright.py releasecheck 1.0.0
FAIL .claude-plugin/plugin.json version: 0.9.0
FAIL evergreen.json version: 0.9.0
FAIL wikiwright.py VERSION: 0.9.0
FAIL SKILL.md metadata.version: 0.9.0
FAIL CHANGELOG entry naming 1.0.0
FAIL TESTS run since v0.9.0: none
releasecheck 1.0.0: 6 problem(s)This wiki describes wikiwright 0.9.0 (master at a809eff) and was last updated on 2026-10-03. The skill is MIT licensed. Report problems in the issues.
Using it
The releases
Contributing
Elsewhere