Skip to content
Copper, a black cat in a copper collar, grips two curved pipes at a dispatch desk in a rain-soaked night office, with rotary telephones, filing cabinets, and a wall-sized route board of copper and teal pipes.

Philadelphia Command Plumbing · Night shift

Stop calling subprocess like an emergency plumber.

Cuprum runs external commands from Python without handing strings to a shell. Programs come from an approved catalogue, arguments become an exact argv, and every run returns a structured result.

Python 3.12+ · no runtime dependencies · ISC licence · Cuprum 0.2.0-beta2, a beta release on PyPI

Plate 01 Copper takes the night shift at the dispatch desk: every line on the board is a job that knows where it is going.

Work order 00-CU

Every command gets the right fitting.

The shell accepts whatever string it is handed. Cuprum starts from programs you have approved, builds the argument vector in one place, and hands back a job sheet you can inspect.

  1. Approved programs only

    Commands are built from a curated catalogue of Program values. A program outside the catalogue raises UnknownProgramError before anything runs.

    sh.make(ECHO)
  2. Arguments, not strings

    Builders produce an exact argv tuple. No shell parses it, so a stray quote or semicolon is just a character.

    echo("-n", "hello")
  3. Async first, sync on request

    Await a command or a pipeline, or call run_sync() in a small script. Both return the same structured result.

    await cmd.run()
  4. No cowboy shutdowns

    Cancellation and timeouts send SIGTERM, wait out a grace period (0.5 s by default), then send SIGKILL and reap the child. Opt in to owning its process group and the descendants go too.

    SIGTERM → grace → SIGKILL

Field manual · page 12

From loose parts to a sound installation.

Make a builder from a catalogued program, call it with arguments, and run the command. RunOutputOptions(echo=True) tees the output to your terminal while Cuprum still captures it. Use run_sync() when the job is small and the kettle is already on.

Install the beta
pip install cuprum==0.2.0b2

Cuprum 0.2.0-beta2 is a beta: the API documented here is settling towards 0.2.0 and may still change. pip installs a pre-release only when it is named, so the command pins the exact version these pages were checked against.

Run your first command
dispatch.py Job #0042
import asyncio

from cuprum import ECHO, RunOutputOptions, sh


async def main() -> None:
    # Pick a known fitting from the catalogue.
    echo = sh.make(ECHO)
    cmd = echo("-n", "hello, cuprum!")

    result = await cmd.run(output=RunOutputOptions(echo=True))
    if result.ok:
        print()
        print(f"exit {result.exit_code}, captured {result.stdout!r}")


asyncio.run(main())
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12

Completed works

Receipts, not vibes.

Every run returns a CommandResult. These are its fields.

ok
Did it work?
exit_code
How did it end?
stdout
What did it say?
stderr
What did it mutter?
pid
Who was responsible?
duration
How long did it take?

The rest of the office

Open the drawers.

The front window shows what Cuprum is for. The drawers hold the detail: runnable examples, the engineering plans, the optional native annex, and a public works board.

Drawer A Verified

Worked examples

Task-shaped job sheets with complete code, expected output, and the failure path.

Drawer B

Internals

How a command is constructed, authorized, run, observed, and torn down, with the invariants that hold throughout.

Annex R Technical preview

Rust extension

An optional native pathway for inter-stage pipeline pumping. Python remains the reference behaviour.

Board 1

Roadmap

What is shipped, what is in progress, and what is only proposed, each with its evidence.

Annex S Proposed

Setwork

A proposed companion that generates typed adapters returning Cuprum commands. Design only; nothing to install.

In a rainy future city, a plumber in a patched orange work jacket pastes pipe-route diagrams onto an alley wall while surveillance drones sweep searchlights overhead and Copper keeps watch from a fire escape.
Exhibit A · suspiciously legible CI glue

Wanted by the authorities

For making shell scripts comprehensible.

Somewhere between Bash and a full orchestration platform there was a perfectly reasonable Python program trying to get out. Cuprum is for that program.

  • Deployment helpers that say which step failed, with its exit code and stderr.
  • CI glue with names, types, and an opt-in GitHub Actions sink that folds each run into a log group.
  • Maintenance scripts whose allowlist a reviewer can read in one place.

“I keep a spare tee behind the filing cabinet. You’d be amazed.”

Copper, senior pipeline dispatcher

No call-out charge

Send for Cuprum.

Typed command execution for Python. The pipes have suffered enough.