Skip to content
jlumbrosoPublic

About

more, less, and now moreover — a pager for readers who can't press space. Cursor-based, non-interactive pagination for LLMs, agents, and scripts. Built in public, deliberations first.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

moreover

crates.io API docs Release build Tests: source Homebrew tap License: MIT

more, less, and now moreover — a pager for readers who can't press space.

moreover is a non-interactive pager for LLMs, agents, and scripts. It saves input from a pipe or file and prints a page. If more remains, it returns a cursor that another invocation can use to read the next page.

$ big-output | moreover -10
[first 10 lines]
<moreover: page 1, 10/123 lines, cursor: ae2e>

$ moreover -c ae2e --all
[the remaining 113 lines]
<moreover: 123/123 lines, cursor: null>

The trailer reports progress and the cursor for the next page. cursor: null means the saved input is exhausted. Reuse the cursor your invocation printed; ae2e above is an example, not a cursor to copy.

Input must finish before the first page appears. The current version reads the whole input into memory and saves it on disk before printing. Use it for finite output, not an ongoing stream such as tail -f.

Install

Homebrew

Install a prebuilt binary from Jérémie Lumbroso's tap:

$ brew install jlumbroso/tap/moreover

Available for macOS and Linux on ARM64 and x86-64. No Rust toolchain is required. Upgrade to a new release with:

$ brew update && brew upgrade moreover

Cargo

Requires Rust 1.89 or later; no external Rust dependencies.

$ cargo install moreover

To install from a checkout of this repository, use cargo install --path ..

Reading pages

The default page size is 10 lines. Use -25 or --lines 25 for a different line count, --bytes 1024 for bytes, or --all for the rest. Each invocation chooses its own size and unit; a cursor does not retain those options. Byte pages can split lines and encoded characters.

$ moreover output.txt -25
$ moreover -c CURSOR -25 --overlap 3

Replace CURSOR with the printed cursor. --overlap 3 repeats up to three preceding units before the next page; the trailer does not count them again. Resuming the same cursor with the same size, unit, and overlap repeats the same content.

If the printed cursor is lost — scrolled away, or compacted out of a model's context — moreover -c latest recovers it: latest selects the cursor most recently used from the current working directory, so concurrent readers in other directories don't collide. It answers "which stream?", not "how much?": pair it with a page size or --all like any resume (moreover -c latest --all).

Content goes to stdout and the trailer to stderr by default. If your caller captures only stdout, use --trailer stdout. Use --trailer none to suppress the trailer, or --trailer file:PATH to append it to a file. --trailer fd:N writes it to an inherited file descriptor.

Input and cursor records remain in the selected state directory. Its precedence is --state-dir PATH, then MOREOVER_STATE_DIR, then $XDG_STATE_HOME/moreover, then ~/.local/state/moreover. Later invocations need that same saved state. Resuming reads the saved input; changes to the original file do not change it. Reaching the end does not delete saved state.

To page a file named contract, ls, stat, drop, or gc, prefix its name with ./.

The tool's own surfaces

The two authoritative descriptions are the binary's: moreover --help is the IO for humans, moreover contract the IO for models. The sections below are generated from the binary itself (scripts/readme-sync.py), and a test fails the build whenever they drift — the README sources these surfaces; it never paraphrases them.

moreover --help — the option list, for humans
moreover — a pager for readers who can't press space

Usage:
  <producer> | moreover [-N | -n N | --bytes N | --all]     (pipe mode)
  moreover FILE [-N | -n N | --bytes N | --all]             (file mode)
  moreover -c CURSOR [-N | --all] [--overlap N]             (resume; no input needed)

Paging:
  -N, -n N, --lines N   page size in lines (default: 10)
  --bytes N             page size in bytes
  --all                 everything (remaining)
  --overlap N           on resume, reprint the last N units before the new
                        page (context re-anchoring; a no-op on first pages)

Resumption:
  -c, --cursor ID       resume the stream that ID names
                        (a cursor is only valid if moreover printed it —
                        never invent or extrapolate one)
  -c latest             recover this directory's saved continuation
                        selection (renamed from last, which now errors
                        with a pointer here)
  --mint stable|fresh   cursor-id policy, asserted (default: stable —
                        the same resume repeated yields the same next
                        cursor; fresh mints a new id every time)

State:
  --state-dir PATH      spool/cursor store (default: $MOREOVER_STATE_DIR,
                        else $XDG_STATE_HOME/moreover, else ~/.local/state/moreover)

Trailer (the v0 grammar is a compatibility promise):
  --trailer DEST        route the trailer: stderr | stdout |
                        none | fd:N | file:PATH (append)
                        This flag overrides MOREOVER_TRAILER for this call.
                        Without this flag, MOREOVER_TRAILER sets the
                        default; unset or empty uses stderr.
  --schema NAME         trailer schema (default: v0)
  --schema-show         print the active schema's templates and exit
  --schema-template T   render the trailer with template T instead

Desk (subcommands — the standalone world; they never appear in pipes):
  moreover contract     print the model-facing contract
  moreover ls           list this directory's cursors (--everywhere:
                        every directory's, in the selected state dir)
  moreover drop CURSOR  declare a parked stream finished; free what it held
  moreover gc [DAYS]    sweep cursors whose last recorded use is DAYS or
                        more days old (default: 7; 0 removes every
                        record); orphaned input follows. Mint, reuse,
                        and resume attempt to refresh that time; a
                        failed refresh leaves the old one. gc and drop
                        act on the WHOLE selected state directory, not
                        just this working directory's cursors.
  (stat: reserved for the desk, not yet available)
  Desk verbs accept --state-dir like the pipe world does.

Introspection:
  --help                this text
  --version             version
moreover contract — the rules, for models
moreover: contract (v0)

purpose:
  Save input from a pipe or file, print a page, and resume the saved input
  in a later invocation using a printed cursor.
  Input must finish before the first page appears: the whole input is
  read into memory and saved on disk. Unbounded input never reaches a page.

invocations:
  stdin:    <producer> | moreover -10
  file:     moreover FILE -10
  resume:   moreover -c CURSOR --all
  recover:  moreover -c latest
  contract: moreover contract
  list:     moreover ls
  drop:     moreover drop CURSOR
  sweep:    moreover gc [DAYS]
  Resume reads saved input, ignores stdin, and rejects an input file.
  Subcommands never read stdin. stat is reserved, not yet available.
  Prefix a filename matching a subcommand with ./ (for example, ./ls).
  With no file and no cursor, a stdin that is a terminal — or an open
  device delivering neither data nor end-of-file within 2 seconds, the
  shape many agent harnesses give — prints a short guide and exits 2
  instead of blocking forever. An empty but finished input is not that
  case: it earns its 0/0 trailer.

desk verbs:
  ls lists the cursors first minted from this working directory, one
  per line, newest first: ID, page, line (with the saved input's total
  when known), creation mode, the saved input's name, and age. The line
  -c latest would select is marked. If the current recovery cursor was
  minted from another directory, it is listed with that note; the
  listing is not a history of every foreign cursor ever reused here.
  ls --everywhere lists every directory's cursors in the selected state
  directory — one store, not every store on the machine. Listing
  changes nothing, and an unreadable record is noted, not hidden.
  drop CURSOR removes that cursor's record FOR EVERY directory that
  uses it. Saved input still referenced by another cursor is kept;
  otherwise it is freed. Recovery records naming the dropped cursor are
  cleared, so -c latest there selects an older record or reports none.
  Other cursors are unaffected.
  gc DAYS removes every cursor record in the selected state directory —
  all working directories — whose last recorded use was DAYS or more
  days ago; gc 0 removes every record unconditionally. GC uses the
  cursor record's last recorded use time. Minting, reuse, and resume
  attempt to refresh it. If that update fails, GC uses the previous
  timestamp, so a recently used cursor can still be collected. GC then
  frees saved input and recovery records nothing references.
  When a removed record occupies a slot that a kept stable ID's
  derivation walked past, an empty placeholder file is left in its
  place so that stable IDs stay reproducible; placeholders are swept
  once nothing depends on them. A record that cannot be read makes gc
  and drop refuse without removing anything: an unreadable record's
  references are unknown, and guessing could destroy another cursor's
  saved input. gc and drop wait for any in-progress paging in the same
  state directory, and paging waits for them. Both are permanent: a
  removed cursor ID stops resolving, and freed input cannot be resumed.
  Desk verbs accept --state-dir and never print a trailer.

paging:
  Every invocation defaults to 10 lines, including resume.
  -N, -n N, or --lines N selects lines; --bytes N selects bytes.
  --all prints the remainder with line counts; use it without a page size.
  Byte pages can split lines and encoded characters. Changing units on
  resume keeps the saved byte position, which may be inside a line.
  --overlap N repeats up to N preceding units before a resumed page,
  using this invocation's unit. It has no effect on the first page.

trailer (schema v0; a compatibility promise):
  paged form:  <moreover: page {page}, {shown}/{total} {unit}, cursor: {cursor}>
  --all form:  <moreover: {shown}/{total} {unit}, cursor: {cursor}>
  {shown} counts units from the start of saved input through this page's
  end; {total} counts the whole saved input. Both use this invocation's
  unit (lines or bytes). Overlap is not added again. Totals are numeric.
  {page} starts at 1 and advances through successive cursors.
  cursor: null means no input remains after this page; do not resume null.
  Otherwise, use the printed cursor to start the next page.
  --schema v0 selects the only current schema; --schema-show prints it.
  --schema-template T replaces the template using the placeholders above.

output:
  Content goes to stdout.
  --trailer DEST accepts stderr, stdout, none, fd:N, or file:PATH.
  Destination precedence: --trailer > MOREOVER_TRAILER > stderr.
  MOREOVER_TRAILER accepts the same destinations and sets the default
  for invocations that inherit it. Unset or empty uses stderr.
  An explicit --trailer overrides even an invalid environment value.
  If used as the default, an unrecognized environment destination causes
  a usage error before any content is emitted.
  The --help, --version, and --schema-show flags and the contract
  subcommand ignore MOREOVER_TRAILER.
  stdout places the trailer after the content; none suppresses it;
  fd:N writes to an inherited descriptor; file:PATH appends to a file.

shell redirection (MOREOVER_TRAILER unset; no --trailer flag):
  moreover FILE -10
    Content goes to stdout; the trailer goes to stderr. Capture both.
  moreover FILE -10 2>&1
    Content, trailer, and errors share stdout. A downstream pipe or
    captured value receives the trailer as data alongside the content.
  moreover FILE -10 2>/dev/null
    The trailer and errors are discarded. A cursor may still be saved,
    but its ID is lost from this output.
  For callers that capture only stdout, use --trailer stdout to include
  the trailer after the content. This still mixes content and metadata;
  use --trailer fd:N or file:PATH when they need separate destinations.
  Changing --trailer does not redirect errors; they still use stderr.
  Check the exit status before using the output.

cursors:
  Use a printed cursor ID — never invent or extrapolate one.
  The reserved word latest selects a saved cursor as described below.
  A cursor fixes a position in saved input, not page size, unit, or overlap.
  Resuming a printed cursor ID with the same page size, unit, and overlap
  repeats the same content. The next cursor ID may differ.
  Resumption leaves the original cursor unchanged.
  IDs are case-insensitive; o folds to 0, and i and l fold to 1.
  IDs start at four characters and can be longer.
  --mint stable (the default) and --mint fresh assert the ID policy;
  each is an assertion, valid under any default. Under stable, the next
  cursor ID is determined by the saved input, the byte position, and
  the page number, so repeating the same resume returns the same ID;
  a matching record made under any mode is reused. Under fresh, every
  resume mints a new ID. mode= in a record names its creation mode.
  No mode creates a successor when the trailer says cursor: null.

latest (recovery):
  -c latest resolves this directory's saved recovery selection. Paging
  updates its recovery record when it mints or reissues a continuation
  cursor. A call returning cursor: null does not update that record.
  With no recovery record, the fallback scans readable cursor records
  whose stored directory matches and selects the newest modification
  time; a successful age refresh can change that fallback even at
  exhaustion. No matching record is an error; a damaged recovery record
  is reported as an error rather than silently selecting an older
  stream. latest is resolved again on each call. Other invocations in
  the same working directory and state directory can change its
  selection, including to another stream. Use a printed ID for a fixed
  position. Stop at cursor: null; another -c latest can repeat
  already-read content. Older records without a working directory do
  not match latest; they can still be resumed by their printed IDs.
  The former name last was renamed to latest; -c last is rejected with
  an error naming the replacement, without touching or creating any
  state. That rejection lasts one minor version, then the word is
  simply unknown; do not rely on either behavior of last.

state (first applicable entry wins):
  --state-dir PATH > $MOREOVER_STATE_DIR > $XDG_STATE_HOME/moreover
  > ~/.local/state/moreover
  A cursor requires its record and saved input in the selected directory.
  Keep that state to resume. The original file or producer is not reread.
  Reaching the end does not delete saved state.

exit codes:
  0 success; 1 reported I/O, state, or cursor error; 2 usage or schema error.
  Error messages go to stderr.

Design

Two demonstrations share this small codebase:

  • ADRs4AI — deliberative programming. docs/adr/ records the questions, alternatives, recommendations, and answers behind the tool's design. ADR-0001 covers the name; ADR-0002 is the design space; ADR-0003 is the option surface. The scaffold comes from human-ai-collaboration-template-A.
  • A tool whose primary user is a model. The default trailer supplies a continuation cursor when input remains. An unknown-cursor error explains that only printed cursors are valid. The contract describes paging, state, and cursor rules without requiring the caller to open this README.

Builds on ThirdX — "ThirdX (3X) is a design discipline for model-facing interfaces, after UX and DX — evidence-based design patterns for the interfaces models consume." moreover is its first command-line tool. Related work: Biilmann's Agent Experience (AX), Arcade's Machine Experience Engineering (MX), Anthropic's tool-writing guidance, and work on identifiers for model readers by Dexter and BAML.

License

MIT © Jérémie Lumbroso

Built with Ribbon 5 (Claude, Fable 5) and Lector 6 (GPT-6, Astra).

About

more, less, and now moreover — a pager for readers who can't press space. Cursor-based, non-interactive pagination for LLMs, agents, and scripts. Built in public, deliberations first.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages