python -m kit is the one command line of the workbench: kit/cli.py builds the command parser, hands each to its handler, and gives the installed package its console scripts.
| Field | Value |
|---|---|
| Id | cli |
| Status | Current |
| Kind | kit module |
| Area | Releases and repository |
| Run | python -m kit, python -m kit help, scrolls-workbench, scrolls-letterread, scrolls-letterlines, scrolls-letterdemo, scrolls-start (the installed package's console scripts), from kit import cli |
| Code | kit/cli.py (build_parser, KitParser, GROUPS, cmd_help, _quick_start, _unknown, _parser, _parse, main, _console, workbench_main, letterread_main, letterlines_main, letterdemo_main, start_main, import_advice, _shape, CONSOLE_SCRIPTS, READER_INSTALL, _PARSERS), kit/__main__.py, kit/__init__.py |
| Tests | tests/test_cli_parser_cache.py, tests/test_packaging.py, tests/test_localcost_regressions.py, tests/test_repo.py, tests/test_letterdemo.py, tests/test_letter_scripts.py |
| Needs | Python 3.10 or newer (requires-python in pyproject.toml). The entry point and every module it imports at start are standard library only; a command that needs numpy, tifffile, zarr, Pillow or scipy imports them when it runs (see its page) |
| Runs on | Any machine. python -m kit prizes --json took 0.19 s wall (measured 2026-10-16, cloud container, Python 3.13.16); building the parser once and reusing it brought a repeated main() from 13 ms to 2 ms per call (commit 88fbee4's message; machine not recorded) |
| Network | None of its own. Some commands contact hosts (such as fetch and run all --refresh); each command's page says which |
| Writes | Nothing of its own. Each command's writes are on its page |
| Go-ahead | Not needed to run the entry point. A command that posts, uploads or rents compute needs Chase's yes first (the go-ahead rule, hard rule 8); its page says so |
| Released | Public main since 2026-10-06 (2daee8e); the console scripts since 2026-10-14 (2582ab7). Since 2026-10-20 the README's "Released tools" lists the command-line tools as one entry, Scrolls kit (scrolls-kit in docs/releases.json), with one line per tool |
| Release id | scrolls-kit |
| Guide | CHANGELOG.md (what each package version holds and how to install it) |
| Checked | 2026-10-23 against commit cc03099 |
Concept illustration: the toolbox's motifs on one tray. No scan data.
Every python -m kit command, from prizes to run all, starts here. kit/__main__.py calls main() in kit/cli.py and exits with what it returns. main() takes the command line, picks the command (and its action, for the eight commands that have actions, such as run init or provenance check), and calls that command's handler: a cmd_ function in kit/cli.py, or releases.run in kit/releases.py. The handler's return value is the exit code.
This page covers the entry point itself: the parser, the command list, the console scripts of the installed package scrolls-workbench, the parser cache and the package version. Each command's options, outputs and errors are on that command's page, linked from the table under Commands and options. The cmd_ functions belong to those pages too.
kit/__init__.py holds the package's docstring and __version__ ("0.2.0" at this commit), which pyproject.toml reads as the package version and which the letter reader writes into its provenance (kit.letterread, kit.letterlines and kit.letterexport import it as KIT_VERSION). The package wraps the official pipeline in ScrollPrize/villa; it does not reimplement it.
The entry point makes no claim of its own. Nothing it prints is a result about ink: each command's page says what its output may and may not be called.
- To find which commands exist and what each does:
python -m kit --help, or the table below. - To run any workbench command from a clone of the repository (
python -m kit COMMAND), or from an installed package (scrolls-workbench COMMAND). - To call a command from Python with its exact command-line behavior:
cli.main([...]).
Do not use it:
- To learn one command's options: read that command's page (
python -m kit.tooldocs show ID), or runpython -m kit COMMAND --help. - For a
python -m kit.MODULEentry point such astooldocs: those have their own parsers and pages and are not commands ofpython -m kit. - For the scripts under
scripts/: they run on their own (bash scripts/X.sh,python scripts/X.py), each with its own page.
cd SCROLLS
python -m kit --help
python -m kit prizes --jsonSCROLLS: the root of a clone of the repository.python -m kitfinds the package only from there (or when the package is installed).
Run on 2026-10-23 in the checkout, --help printed the usage line and the commands grouped by task, starting:
usage: python -m kit [-h] COMMAND ...
The Scrolls workbench: one command line for the Vesuvius Challenge tools in this repository.
Start and set up:
start open Scrolls Starter: setup, practice and guided next steps
doctor check GPU, disk, tools and villa paths
and prizes --json printed the dated prize snapshot, starting { "checked": "2026-10-11", "source": "https://scrollprize.org/prizes", ..., with exit code 0. It worked when the exit code is 0. An unknown command prints python -m kit: error: no command '...'. Did you mean: ...? and exits with 2. python -m kit help tifxyz prints one command's options and the worked example from its page.
The entry point has no result to control. These checks keep it honest:
- The cached parser gives the same answer as a fresh one.
tests/test_cli_parser_cache.pycompares the namespace from_parse(_parser(), argv)withbuild_parser().parse_args(argv), and checks that a handler that changes a list default (such asrowscore --reverse) does not leak it into the next call. - Every documented command exists.
tests/test_repo.py(test_documented_kit_commands_exist) fails when a tracked.md,.sh,.txtor.pyfile names apython -m kitcommand the parser does not have. - Every command and action has a page.
python -m kit.tooldocs checkfails when a command or action is on no page's Run field, or when a page's usage block, options or defaults differ from this parser (tooldocs). - Console scripts match.
tests/test_packaging.pychecks that every script inpyproject.toml's[project.scripts]points at a function here, that the one-command scripts are exactlyCONSOLE_SCRIPTS, and that each shows its own name in its usage and errors.
Claim label: None; the entry point makes no claim, and each command's page labels its own output.
usage: python -m kit [-h] COMMAND ...
| Option | Default | Meaning |
|---|---|---|
command |
required | One of the commands below. Everything after it goes to that command's parser. A word that is not a command prints the closest names (no command 'tifxzy'. Did you mean: tifxyz?) and exits 2 |
-h, --help |
off | Print the usage, the module docstring and the commands grouped by task (start and set up; find and fetch public data; segments and geometry; ink maps; letters; records and repository), with worked examples, then exit 0 |
scrolls-workbench takes the same arguments and shows scrolls-workbench in its usage and errors (main(argv, prog="scrolls-workbench")).
usage: python -m kit help [-h] [topic]
| Option | Default | Meaning |
|---|---|---|
topic |
none | A command, such as tifxyz: print its --help and the Quick start of its tool page. Without it, print the grouped command list (the same as --help) |
Each command's --help line, its actions, its handler and the page that documents it.
| Command | Actions | Handler | Page | What it does (--help) |
|---|---|---|---|---|
help |
none | cmd_help |
cli | list the commands by task, or show one command's options and worked example |
start |
none | starter.run (in kit/starter.py) |
starter | open Scrolls Starter: setup, practice and guided next steps |
prizes |
none | cmd_prizes |
prizes | open prizes from the dated snapshot |
live |
none | termview.main (in kit/termview.py) |
termstyle | run a command (after --) or page piped output in a full-screen, mouse-driven view in the Scrolls look |
style |
none | termstyle.main (in kit/termstyle.py) |
termstyle | the Scrolls terminal look: hook install, remove or status (style every script in a checkout), demo, pipe |
stages |
none | stagereg.main (in kit/stagereg.py) |
stagereg | the stage registry: every option of every stage (ours, community, team), its licence and maturity, and what a workflow would pick (list, show, pick, check) |
doctor |
none | cmd_doctor |
doctor | check GPU, disk, tools and villa paths |
plan |
none | cmd_plan |
plan | print a First Letters run plan for an eligible scroll |
cost |
none | cmd_cost |
plan | estimate run cost in USD |
find |
none | find.cmd_find (in kit/find.py) |
find | everything public for one scroll: volumes, segments, predictions, center lines and Hugging Face matches |
hopbench |
refs, run |
hopbench.cmd_hopbench (in kit/hopbench.py) |
hopbench | planted-hop benchmark: does a surface prediction keep touching sheets apart on PHerc0139's hand-traced windings |
labels |
none | labels.cmd_labels (in kit/labels.py) |
labels | published ink labels per segment, with which public models trained on them |
centerline |
list, get |
centerline.cmd_centerline (in kit/centerline.py) |
centerline | get a scroll's center line into your VC3D project as umbilicus.json |
cite |
none | find.cmd_cite (in kit/find.py) |
find | credit and licence lines for a scroll's volumes and center lines |
fetch |
none | cmd_fetch |
fetch | mirror a public bucket prefix over HTTPS (no AWS CLI needed) |
s3relay |
none | vc3dfix.cmd_s3relay (in kit/vc3dfix.py) |
vc3dfix | run VC3D through a local relay that keeps S3's false 404s from blanking chunks |
vc3dcache |
none | vc3dfix.cmd_vc3dcache (in kit/vc3dfix.py) |
vc3dfix | find and delete false empty-chunk markers in VC3D's remote chunk cache |
verify |
none | cmd_verify |
verify | compare two ink maps (e.g. CPU vs MPS) with a control |
rowscore |
none | cmd_rowscore |
rowscore | text-row periodicity triage score for ink maps (Bullo27's method) |
auc |
none | cmd_auc |
auc | pixel AUC of an ink map against a segment's published labels |
fibertensor |
train, predict |
cmd_fibertensor |
fibertensor | fiber-orientation (structure tensor) features and a tiny CPU ink reader |
sheetcheck |
maps, volume, demo, summary |
cmd_sheetcheck |
sheetcheck | label-free self-check of an ink map: on the traced sheet, or smeared across sheets, along the mask edge or in empty space |
hpscore |
none | cmd_hpscore |
hpscore | letter-scale score: 48 um high-passed correlation with the labels (Scheirer) |
overlap |
none | cmd_overlap |
overlap | do two segments trace the same papyrus? mesh gaps and label agreement |
expatlas |
models, segments, pair, matrix, plant, show |
cmd_expatlas |
expatlas | Exposure Atlas: how much of each labeled segment a released ink model's training meshes already cover, in one scroll frame; the leakage matrix |
collate |
none | cmd_collate |
overlap | two traces of one sheet as two copies: do their ink maps agree, against a control? |
atlas |
build, show |
atlas.cmd_atlas (in kit/atlas.py) |
atlas | Segment Atlas: area, isometry, holes and folds of every published segment |
coverage |
none | coverage.cmd_coverage (in kit/coverage.py) |
coverage | Winding coverage: which turns and heights a scroll's segments trace, and the gaps |
tifxyz |
info, stretch, check, obj, preview, facing, chunks, diff |
tifxyz.cmd_tifxyz (in kit/tifxyz.py) |
tifxyz | look at a tifxyz segment without VC3D: area, isometry, holes and folds, OBJ, picture, which way it faces, chunks it touches, diff of two versions |
zarrbox |
plan, copy, verify |
zarrbox.cmd_zarrbox (in kit/zarrbox.py) |
zarrbox | copy one box of a zarr volume, chunk files byte for byte, with a sha256 manifest; plan it first, verify it later |
sentinel |
scan, queue, check, table, preflight, controls, summarize |
sentinel.cmd_sentinel (in kit/sentinel.py) |
sentinel | Bucket Sentinel: check every new or changed public upload (label canvases against their meshes, pyramids, scales, mesh frames); offset table |
sheetview |
build, serve |
cmd_sheetview |
sheetview | show a volume (3D ink, CT) on a tifxyz sheet's surface in Neuroglancer: a mesh with per-vertex values and a link |
stackcheck |
none | cmd_stackcheck |
stackcheck | check consecutive tifxyz segments against the umbilicus: order, missing sectors, skipped windings, turn overlaps, stack ends (geometry only) |
uvgate |
none | cmd_uvgate (in kit/uvgate_cli.py) |
uvgate | check a flattened sheet's UV triangles (JSON or CSV): no overlap, enough area, optionally one counterclockwise piece (geometry only) |
windconsistency |
none | cmd_windconsistency (in kit/windconsistency.py) |
windconsistency | check that relative winding counts (a to b is k windings) can all hold at once, around every closed path |
axispolish |
none | cmd_axispolish (in kit/axispolish.py) |
axispolish | polish a center line (umbilicus) to the deepest point of the traced sheet on the scan's published normal grids |
windlabel-box |
none | windlabel_box.cmd_windlabel_box (in kit/windlabel_box/__init__.py) |
windlabel-box | relative windings in a box around a point, from a published surface prediction and lasagna grad_mag (model output) |
snap |
none | snap.cmd_snap (in kit/snap.py) |
snap | snap a tifxyz mesh along its normals onto the nearest predicted sheet, the CT sheet midline or a band face, regularized |
labelsnap |
snap, check |
labelsnap.cmd_labelsnap (in kit/labelsnap.py) |
labelsnap | move a tifxyz surface label onto the papyrus recto face: snapped tifxyz, abstain mask, OME-Zarr surface label (1 surface, 2 ignore), VC3D review points |
finesnap |
snap, check |
finesnap.cmd_finesnap (in kit/finesnap.py) |
finesnap | move a tifxyz surface label onto the recto face seen in a finer registered scan of the same scroll (kit labelsnap's products plus source.tif) |
meshaudit |
transforms, canvas, plan, depth |
cmd_meshaudit |
meshaudit | audit published meshes, cross-scan transforms and render depth offsets |
surfacefix |
prepare, apply |
cmd_surfacefix |
surfacefix | experimental region flags and bounded, controlled surface corrections |
provenance |
write, check |
cmd_provenance |
provenance | who ran what, when, from which inputs: a record and one digest |
compute |
none | cmd_compute |
compute | machine time per run, from provenance records (GENChase-style ledger) |
ensemble |
none | cmd_ensemble |
ensemble | average several ink maps of one surface (mean, or rank mean across models) |
gate |
none | cmd_gate |
gate | Gate A: rank readers on PHerc0841's three crops by the rule in kit/gate.py |
layers |
none | cmd_layers |
layers | export a surface-volume zarr to 00.tif, 01.tif, ... (v8in's input) |
shuffle |
none | cmd_shuffle |
layers | depth-shuffle control: copy a surface volume with its layers in a fixed random order |
phantom |
none | cmd_phantom |
phantom | synthetic carbon-ink phantoms with known truth: make, stress-test a detector, calibrate AUC noise |
view |
none | cmd_view |
view | one static HTML page overlaying several ink maps, their disagreement and the labels |
labelsuggest |
map |
labelsuggest.cmd_labelsuggest (in kit/labelsuggest.py) |
labelsuggest | where to paint ink labels next: tiles where readers' ink maps disagree, with matched random and agree tiles as controls |
altrain |
plan, readout |
altrain.cmd_altrain (in kit/altrain.py) |
altrain | active-learning training-run comparison: fine-tune on suggested, uncertain or random tiles with oracle labels; layout, bootstrap and the readout rule |
strokenet |
none | cmd_strokenet |
strokenet | learned text-likeness score of a 2D ink map (StrokeNet), with rotation and IAAFT controls |
volumeview |
none | cmd_volumeview |
volumeview | build a local viewer link from explicit public volume metadata |
segzoo |
none | cmd_segzoo |
segzoo | model zoo: nnU-Net, MedNeXt, Swin UNETR, Auto3DSeg, SegFormer, DINOv3, MedSAM2 and Mamba families, their licenses and fit; Auto3DSeg datalists |
fibers |
scans, stream, filter, parity, build |
cmd_fibers |
fibers | AFV papyrus fibers in a box, streamed from Hugging Face without the full download, or filtered from a downloaded .afv; parity check; stream shards |
scroll2ink |
run, status |
cmd_scroll2ink |
scroll2ink | one command from a public scan and a seed point to a grown, flattened, rendered surface and its ink map with the reverse control (Headless VC3D) |
letterread |
none | cmd_letterread |
letterread | Scroll Text Reader: Greek LineNet or local OCR for installed languages, with controls |
letterlines |
build, score |
cmd_letterlines |
letterlines | training lines for the letter reader from your own transcribed lines, and scores on lines kept apart |
letterdemo |
none | cmd_letterdemo |
letterdemo | offline check of the letter reader: synthetic Greek capitals and a noise image, DEMO PASS or DEMO FAIL |
greeklm |
build, score |
cmd_greeklm |
greeklm | letter n-gram language model for Greek in scriptio continua |
routines |
list, due, status, run, check |
routines.run (in kit/routines.py) |
routines | recurring repository checks any agent runs at session start (kit/routines.py, docs/routines.json) |
stale |
none | stale.run (in kit/stale.py) |
stale | what live pages and registries still say that is no longer true: old handoff rows, gone branches, retired names, dead links, MAP rows (kit/stale.py; read-only) |
integrity |
none | integrity.run (in kit/integrity.py) |
integrity | research integrity checks for Vesuvius claims: ink scores with their controls and held-out status, overclaim wording, preregistration order, citations; each plants faults first (kit/integrity.py; read-only) |
submitcheck |
none | submitcheck.cmd_submitcheck (in kit/submitcheck.py) |
submitcheck | preflight a prize submission folder against the prize page's file rules, quoting each rule |
prclock |
none | villapr.cmd_prclock (in kit/villapr.py) |
villapr | days left before villa's PR time limits close each of a person's open PRs |
prtext |
none | villapr.cmd_prtext (in kit/villapr.py) |
villapr | check a villa PR or issue body against villa's template before posting |
villaports |
scan, diff |
villaports.run (in kit/villaports.py) |
villaports | which references to villa's Python packages in your scripts break at a villa commit (such as villa #2440's removals), and what replaces each (kit/villaports.py; read-only) |
releases |
add, render, check, list, pin, unpin |
releases.run (in kit/releases.py) |
releases | the list of tools released from private work, and the README section made from it |
spaceconnect |
render, check, run, new |
spaceconnect.run (in kit/spaceconnect.py) |
spaceconnect | each Space's Connect your tool panel: render it from connect.json, check every Space has a current one |
vxsummary |
check, fixture |
vxsummary.run (in kit/vxsummary.py) |
snapped-labels | the Snapped Labels Space's voxel-exact summary: check it before it ships, or write the synthetic fixture |
leaderboard |
render, check |
leaderboard.run (in kit/leaderboard.py) |
leaderboard | the ink reader leaderboard, docs/leaderboard.md, written from docs/results.json |
run |
init, status, cost, record, check, list |
cmd_run |
run-ledger | local experiment ledger (experiments/, gitignored) |
run |
local, backfill |
cmd_run |
run-local | local: run a local command and log wall time, CPU time and electricity cost; backfill: add estimated local-run costs from a JSON list of logged durations |
run |
modal-row |
cmd_run |
modal-row | append one Modal job's row to a Modal ledger CSV, once per key (kit/modalledger.py) |
run |
all, watch, catalog |
cmd_run |
batch | all: AutoPipeline over every released scan: catalog, per-scan config, priced plan, resumable queue (kit/autobatch.py); launches nothing. watch: for a scheduled routine: find new or changed volumes in the bucket, queue them priced in dry run, print them (launches nothing). catalog: write the catalog of every volume in the open-data bucket |
run takes one option of its own before the action, --root ROOT (default experiments, ledger.DEFAULT_ROOT), the ledger folder, as in python -m kit run --root ROOT local SLUG -- COMMAND. AutoPipeline (AP) is the released tool that runs villa's pipeline from a released scan to a scored ink map; its page is pending.
pyproject.toml's [project.scripts] installs four scripts with the package scrolls-workbench:
| Script | Function | What it runs |
|---|---|---|
scrolls-workbench |
kit.cli:workbench_main |
Every command, as python -m kit does. gate and releases read the repository's docs/ and README.md, so they need a clone |
scrolls-letterread |
kit.cli:letterread_main |
python -m kit letterread alone |
scrolls-letterlines |
kit.cli:letterlines_main |
python -m kit letterlines alone (its actions build and score) |
scrolls-letterdemo |
kit.cli:letterdemo_main |
python -m kit letterdemo alone |
scrolls-start |
kit.cli:start_main |
python -m kit start, the local beginner app (starter) |
A one-command script takes that command's own arguments, with no command word: scrolls-letterdemo --quiet, not scrolls-letterdemo letterdemo --quiet. Its usage and errors show the script's name (usage: scrolls-letterlines [-h] {build,score} ...).
The entry point reads none. Commands that read environment variables list them on their own pages.
from kit import cli
code = cli.main(["prizes", "--json"]) # the same as python -m kit prizes --json; returns the exit code| Function | Returns | Raises | Notes |
|---|---|---|---|
main(argv=None, prog="python -m kit", sub_progs=None) |
The handler's return value: the exit code (an int, or None, which sys.exit treats as 0) |
SystemExit(2) on a usage error and SystemExit(0) after --help, from argparse; whatever the handler raises |
argv defaults to sys.argv[1:]. Uses the cached parser. For run ... -- COMMAND, parses the part before -- and, when the action is local, appends everything after -- to the child command unchanged |
build_parser(prog="python -m kit", sub_progs=None) |
A new argparse.ArgumentParser with every command |
Nothing of its own | sub_progs maps a command to the program name its usage and errors show; letterread, letterlines and letterdemo honor it. tooldocs reads this parser to check every page |
_parser(prog="python -m kit", sub_progs=None) |
The parser for (prog, sub_progs), built once per process and kept in _PARSERS |
Nothing of its own | The parser is over a thousand add_argument calls; pipelines and tests call main() many times in one process |
_parse(parser, argv) |
A deep copy of parser.parse_args(argv) |
SystemExit from argparse |
The copy keeps a handler that changes a list default from leaking it into the next call |
_console(script, argv=None) |
The handler's return value | KeyError for a script not in CONSOLE_SCRIPTS; SystemExit from argparse |
Runs the one command of a console script, with a fresh parser whose usage shows the script's name |
workbench_main(argv=None) |
As main |
As main |
Console script scrolls-workbench |
letterread_main(argv=None), letterlines_main(argv=None), letterdemo_main(argv=None) |
As _console |
As _console |
Console scripts scrolls-letterread, scrolls-letterlines, scrolls-letterdemo |
import_advice(exc) |
str: the ImportError's text, plus the install line for the missing package |
Nothing | Adds READER_INSTALL when numpy, Pillow (PIL), tifffile or imagecodecs is missing, and a zarr line when zarr is; used by letterread and letterlines |
_shape(v) |
(v[0], v[1]), or (384, 384) when v is empty |
Nothing | The default phantom shape of python -m kit phantom (phantom) |
CONSOLE_SCRIPTS |
dict | Console script name to command: scrolls-letterread, scrolls-letterlines, scrolls-letterdemo |
|
READER_INSTALL |
str | python -m pip install "scrolls-workbench[reader]" (numpy, pillow, tifffile and imagecodecs) |
|
kit.__version__ |
str | The package version, "0.2.0" at this commit; pyproject.toml reads it ([tool.setuptools.dynamic]) |
| start_main(argv=None) | As main | As main | Runs Scrolls Starter through _console |
| KitParser | An argparse.ArgumentParser whose format_help lists the commands under GROUPS' task headings, wrapped to the terminal width, with worked examples | Nothing of its own | Only the top level uses it; each command's own parser is a plain ArgumentParser (parser_class in build_parser) |
| GROUPS | tuple of (heading, commands) | | The task groups --help shows; a command in no group is listed under "Other" |
| cmd_help(args) | 0, or 2 for an unknown topic (with _unknown's message) | Nothing | python -m kit help prints the grouped list; help COMMAND prints that command's --help and the Quick start of its tool page (_quick_start) |
| _quick_start(command) | str or None | Nothing | The Quick start section of the docs/tools/ page whose Run field names python -m kit COMMAND |
| _unknown(word, choices, prog) | 2 | Nothing | Prints PROG: error: no command 'WORD'. Did you mean: ...? The list: PROG --help |
The command line only: a command name, its action where it has actions, and that command's arguments. main(argv) takes the same as a list of strings. Everything after -- in run ... local SLUG -- COMMAND is passed to the child command unchanged, including words that look like options and literal text such as $(not executed).
The entry point prints nothing of its own except argparse's usage, help and errors (on stdout for --help, on stderr for errors). Each command's output is on its page.
| Exit code | Meaning |
|---|---|
0 |
--help, or the command's handler returned 0 (or None) |
1 |
Python could not find the package (No module named kit), the output pipe was closed before the command finished writing (no traceback), or a command's own failure or an uncaught error with a traceback; see the command's page |
2 |
A usage error from argparse (no command, an unknown command or action, a bad option), or a command's own bad-input exit; see the command's page |
3 |
Only from commands that use it, such as verify (pass-uncontrolled) and run watch --exit-code (something new queued) |
| What you see | Why | What to do |
|---|---|---|
python -m kit: error: the following arguments are required: command, exit code 2 |
No command given | Add a command; python -m kit --help lists them |
python -m kit: error: no command 'NAME'. Did you mean: A, B? The list: python -m kit --help, exit code 2 (also from help NAME) |
A misspelled command, or one that exists only on another branch or in Scrolls-private | Use a suggested name, or pick one from python -m kit --help |
python -m kit run: error: the following arguments are required: action (and the same for the other commands with actions), exit code 2 |
A command with actions given without one | Add the action, such as run init or provenance check |
scrolls-workbench: error: ..., scrolls-letterread: error: ... and the like, exit code 2 |
The same usage errors under an installed script's name | As above |
/usr/bin/python3: No module named kit, exit code 1 |
python -m kit run outside the clone's root, with the package not installed |
cd to the repository root, or install the package as CHANGELOG.md shows and use scrolls-workbench |
No module named numpy; python -m pip install "scrolls-workbench[reader]" (numpy, pillow, tifffile and imagecodecs) |
letterread or letterlines needs a package the reader extra brings (import_advice) |
Run the install line it prints |
No module named zarr; python -m pip install zarr (or "scrolls-workbench[all]", Python 3.11 or newer) |
A letter command was given a zarr input without zarr installed (import_advice) |
Install zarr, on Python 3.11 or newer for zarr 3 |
| No message, exit code 1, and output cut short | A reader that stops early, as in python -m kit prizes --json | head -1, closed the pipe; the command ends quietly, as Python's documentation advises for SIGPIPE |
Nothing, when the cut was meant; otherwise write the output to a file |
| A command's own message | That command's input or environment | See the command's page, linked in the table above |
python -m kitrunskit/__main__.py, which importsmainfromkit/cli.pyand callssys.exit(main()).- Importing
kit/cli.pyimports the standard-library-only modules its handlers call (prizes,plan,ledger,verify,aucand the others in its import line). Modules that need numpy or other packages import them inside functions, so the entry point starts without them. main()gets the parser from_parser(prog, sub_progs). The first call builds it withbuild_parserand stores it in_PARSERSunder the key(prog, sorted sub_progs); later calls in the same process reuse it.- It parses the arguments with
_parse, which deep-copies the namespace so a handler that changes a list default cannot affect a later call, exactly as with a fresh parser. - Special case: when the first word is
runand the arguments hold--, it parses only the part before--. If the action islocal, it appends everything after--to the child command (args.cmd); otherwise it parses the whole line as usual. This works around Python 3.10's argparse, which mishandles a trailingnargs="*"after wrapper options (commit7492129). - It calls
args.func(args), the handler thatset_defaults(func=...)attached to the command, through_run, which puts the typed command line inkit.verify.RUNNINGwhile it runs (so an install hint names the command actually run) and flushes stdout inside the call. ABrokenPipeErrorthere, from a reader that closed the pipe early, points stdout at the null device and returns 1 with no traceback. Otherwise the handler's value is the exit code. - A one-command console script (
_console) builds a fresh parser withsub_progs={command: script}, takes that command's sub-parser, parses the arguments with it, and calls its handler.
python -m kit prizes --json took 0.19 s wall in a cloud container (measured 2026-10-16, Python 3.13.16). tests/test_cli_parser_cache.py requires 20 calls of main(["prizes", "--json"]) in one process to take under 5 s. Commit 88fbee4's message gives a repeated main() at 13 ms per call before the parser cache and 2 ms after (machine not recorded). Memory: Not measured. The entry point runs anywhere; where a command must run (a laptop, or Modal for GPU work and long CPU jobs, the Modal rule, hard rule 11) is on its page.
- Command:
python -m kit COMMAND [ACTION] [OPTIONS]from the repository root;python -m kit --helplists the commands, andpython -m kit COMMAND --helpgives one command's usage. Machine-readable output is each command's--jsonwhere it has one. - Parse: the command's own output, as its page says. From the entry point itself, only the exit code and argparse's
error:line on stderr. - Rerun: The entry point changes nothing; whether a rerun is safe depends on the command (see its page's For agents).
- Success: Exit code 0, then the command's own success values.
- Not a pass: Exit code 0 from a command whose page says 0 is not enough by itself, such as an
aucwith a nullforward.auc; exit code 3 fromverify(no control). - Go-ahead: Nothing for the entry point. Commands that post, upload or rent compute need Chase's yes first; their pages say which.
- Never: Edit a command's options without updating its page in the same change (the tool-pages rule, hard rule 16); add a command without a page; run a GPU job through any command on a laptop CPU instead of Modal.
python -m unittest tests.test_cli_parser_cache tests.test_packaging tests.test_localcost_regressions tests.test_kit.CliTest -vtests/test_cli_parser_cache.py covers the parser cache (one parser per key, the same namespace as a fresh parser, no leaking list defaults, 20 calls under 5 s). tests/test_packaging.py covers the console scripts (each in pyproject.toml points at a function here, the one-command scripts equal CONSOLE_SCRIPTS, each shows its own name in usage and errors, scrolls-workbench runs prizes and refuses an unknown command with exit code 2) and that the package version comes from kit.__version__. tests/test_localcost_regressions.py covers the run ... local SLUG -- COMMAND split. tests/test_kit.py's CliTest checks that the module docstring names every command and that a reader that stops early ends prizes --json and cost with exit code 1 and no traceback. tests/test_repo.py checks that every documented python -m kit command exists, tests/test_letterdemo.py runs the scrolls-letterdemo function, and tests/test_letter_scripts.py checks that CITATION.cff's version equals __version__. These ran in 0.14 s here (16 tests, 2026-10-16). They do not install the package or run the console scripts as installed executables; the CI workflow does that (its page is workbench-package).
- 2026-10-23:
--helpgroups the commands by task with worked examples,help [COMMAND]shows a command's options and its page's Quick start, and an unknown command suggests the closest names; new commandsfind,centerline,cite,prclock,prtextandtifxyz(3376687). - 2026-10-21: the usage block and the command table list
start,volumeview,segzoo,routines,staleandleaderboard, which they had left out; the checker now compares both (bbecb37). - 2026-10-17: a command whose output pipe closes early (
python -m kit prizes | head -1) ends with exit code 1 and no traceback; before, it printed aBrokenPipeErrortraceback (f3b1ac8). - 2026-10-17: the module docstring's command list names
meshauditandstrokenet, which it had left out (e7dce03). - 2026-10-16: package version 0.2.0 in
kit/__init__.py, withCHANGELOG.md(00b0097). - 2026-10-16: the parser is built once per process and each parsed namespace is deep-copied; a repeated
main()went from 13 ms to 2 ms per call (88fbee4). - 2026-10-14: the installable package
scrolls-workbenchwith console scriptsscrolls-workbench,scrolls-letterread,scrolls-letterlinesandscrolls-letterdemo(2582ab7). - 2026-10-07:
run ... local SLUG -- COMMANDparses correctly on Python 3.10 (7492129). - 2026-10-06: first version of
python -m kit(2daee8e).
- tooldocs: checks every page against this parser.
- workbench-package:
pyproject.tomland the CI workflow that installs the package and its console scripts. - run-ledger, run-local, modal-row and batch: the four pages that split the actions of
run. - CHANGELOG.md: what each package version holds.
- MAP.md: one line per module, to find a path.
