Skip to content
Work order 001 · Getting started Verified

Getting started

Install Cuprum, run one approved command, and read the result it hands back.

Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux

Plate 02 Copper stamps a single job sheet. One command, one result.

01 — Install

Install the beta

Cuprum needs Python 3.12 or later and nothing else at run time. The pure-Python wheel needs no Rust toolchain.

Everything on these pages describes Cuprum 0.2.0-beta2, a beta release on PyPI. Most of what is shown here is new since 0.1.0, including RunOutputOptions, ScopeConfig, and ProgramCatalogue.from_programs(). Install it by its exact version:

pip
pip install cuprum==0.2.0b2
uv
uv add cuprum==0.2.0b2

02 — First command

Run one approved command

A builder comes from the catalogue, a call makes an exact argv, and running it returns a CommandResult.

first_command.py Synchronous
from cuprum import ECHO, sh

cmd = sh.make(ECHO)("-n", "hello, cuprum!")
print(cmd.argv_with_program)

result = cmd.run_sync()
print(result.ok, result.exit_code, repr(result.stdout))
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux
Expected outputstdout
('echo', '-n', 'hello, cuprum!')
True 0 'hello, cuprum!'

ECHO is one of the programs in Cuprum's default catalogue, alongside LS, GIT, RSYNC, TAR, and DOC_TOOL. Positional arguments are stringified in order; keyword arguments become --flag=value; None is rejected. With no scope established, every catalogued program may run — the permissive default eases adoption, and the policy section shows how to narrow it.

Fig. 01

What happens between a builder call and a result?

  1. Command or builder sh.make(ECHO) Looks ECHO up in the catalogue and returns a builder.
    passes builder to the next stage
  2. Command echo("-n", "hello, cuprum!") Builds a SafeCmd: program, argv, and project metadata.
    passes SafeCmd to the next stage
  3. Authorization boundary Current scope run_sync() checks the program against the active allowlist.
    passes allowed to the next stage
  4. Process echo -n "hello, cuprum!" Spawned directly with the argv tuple; no shell.
    passes status, bytes to the next stage
  5. Result CommandResult ok, exit_code, stdout, stderr, pid, duration.
  • Program not in the catalogue sh.make raises UnknownProgramError. No command is built.
  • Program outside the current scope run_sync() raises ForbiddenProgramError before anything is spawned.
Construction and authorization are separate checks: the catalogue decides what can be built; the scope decides what may run here. Source: cuprum/sh.py and cuprum/context/ at v0.2.0-beta2

03 — Async

Await it, and watch it

The same command awaits in async code. RunOutputOptions decides whether output is captured, echoed, or both.

dispatch.py Async
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 · Linux
Expected outputterminal
hello, cuprum!
exit 0, captured 'hello, cuprum!'

The first line is the echo: echo=True tees the child's output to your terminal as it arrives. The second is the capture, which still happens. capture=True, echo=False is the default; echo_stdout and echo_stderr choose per stream.

Cancelling the awaiting task sends SIGTERM, waits half a second, then sends SIGKILL and reaps the child.

04 — Policy

Narrow what may run

A scope says which catalogued programs may run inside a block of code. Anything else is refused before it starts.

scoped.py Scope
from cuprum import ECHO, LS, ForbiddenProgramError, ScopeConfig, scoped, sh

with scoped(ScopeConfig(allowlist=frozenset([ECHO]))):
    print(sh.make(ECHO)("-n", "allowed").run_sync().stdout)
    try:
        sh.make(LS)().run_sync()
    except ForbiddenProgramError as exc:
        print(f"refused: {exc}")
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux
Expected outputstdout
allowed
refused: Program 'ls' is not allowed in the current context
Also expectedstderr, logger cuprum.context
Program ls denied by context allowlist restricted_state=True

Scopes nest, and a nested scope can only narrow its parent: its allowlist is intersected with the one it inherits. The scope lives in a ContextVar, so it follows the current thread and async task rather than the whole process, and it is restored when the block exits, even on an exception.

A program that is not in the catalogue at all is refused earlier, when the builder is made:

unknown.py Catalogue
from cuprum import Program, UnknownProgramError, sh

try:
    sh.make(Program("rm"))
except UnknownProgramError as exc:
    print(f"refused: {exc}")
Verified 2026-10-19 · Cuprum 0.2.0b2 · Python 3.12 · Linux
Expected outputstdout
refused: Program 'rm' is not in the catalogue allowlist

05 — Next

Where to go next

Example 001

Run an approved command

Build a one-program catalogue for your own tool, feed it stdin, and see both refusal paths.

BeginnerCatalogue
Drawer B

Internals

The lifecycle of one command, the pipeline plumbing, and the invariants that hold throughout.

Reference

The users' guide

The complete reference, on GitHub, including telemetry adapters, timeouts, and concurrency.