Skip to main content

Emix

PyPI CI Python License: MIT

Emix lets a modern computer present itself as an older one — and, when you ask it to, run that older computer's real software on your real files.

Those are two different things, and Emix keeps them clearly apart:

  • A personality is not an emulator. emix cpm reproduces CP/M's commands, syntax, output formats and error messages in Python, over ordinary host files. No 8080 is involved.
  • An application is. A>ED LETTER.TXT loads a real ED.COM and executes real Z80 instructions through RunCPM, on a document staged from your own folder. Emix owns the drives, the filenames and what comes back out; it does not own the CPU.

Emix ships no operating systems and no applications — you supply software you already have, and Emix supplies the knowledge of how to drive it.

The full guide is docs/MANUAL.md.

Four personalities ship today:

Personality System Prompt Vocabulary
cpm Digital Research CP/M 2.2 A> DIR, ERA, REN, TYPE, USER, plus PIP and STAT
vms DEC VAX/VMS DCL $ DIRECTORY, TYPE, COPY, RENAME, DELETE, SET, SHOW
cms IBM VM/CMS (none) LISTFILE, TYPE, COPYFILE, RENAME, ERASE, QUERY
wopr WarGames (1983), fictional (none) LIST, DISPLAY, DUPLICATE, REDESIGNATE, PURGE, GAMES

The first three are real systems, and are deterministic and offline. wopr is the war room computer from WarGames: the only personality that cannot be historically inaccurate, the only one that reaches no files at all, and the only one that will refuse to play with you:

PLAY GLOBAL THERMONUCLEAR WAR

A STRANGE GAME.
THE ONLY WINNING MOVE IS NOT TO PLAY.

HOW ABOUT A NICE GAME OF CHESS?

emix --setup reports what is configured and what is missing, and changes nothing.

WOPR is also the one place a language model is welcome. CONVERSE ON, with emix-shell[ai] installed, lets it answer in its own words and play the games on its list — off unless you ask, and unable to touch anything either way, because WOPR has no files to reach and neither does anything answering as it. There is no Emix server and no account: your key goes from your own environment straight to Anthropic, as it would from any local tool.

They are not four programs. They are four vocabularies over one engine, and the differences between them — CP/M's NEW=OLD argument order, DCL's abbreviations and /QUALIFIERS, CMS's three-token FILENAME FILETYPE FILEMODE — are what the engine exists to express.

TRANSLATE is where that becomes visible, because it answers in every personality at once:

A>TRANSLATE COPY
TO COPY A FILE:
  CP/M 2.2  PIP NEW=OLD
  OPENVMS   COPY
  VM/CMS    COPYFILE
  WOPR      DUPLICATE NEW OLD

A>TRANSLATE CD
TO CHANGE DIRECTORY:
  CP/M 2.2  -- NO EQUIVALENT
            CP/M 2.2 HAD NO DIRECTORIES AT ALL, ONLY DRIVES A: TO P:
  OPENVMS   SET DEFAULT
  VM/CMS    -- NO EQUIVALENT
            CMS REACHED OTHER DISKS BY FILEMODE LETTER RATHER THAN BY MOVING
  WOPR      -- NO EQUIVALENT
            WOPR NEVER EXISTED, SO IT NEVER NEEDED ANYWHERE TO GO

A gap is reported rather than skipped. What a system cannot say is usually the more interesting fact about it, and it is the one a table of equivalents would quietly hide.

BECOME goes further and hands the session over, keeping your files exactly where they are:

A>DIR
A: NOTES    TXT

A>BECOME VMS
$ DIRECTORY
Directory DKA0:[000000]

NOTES.TXT;1

$ BECOME CMS
Ready;
LISTFILE
NOTES    TXT      A1

One file, one directory, three real vocabularies, one session. The drives come across renamed — the same host folder answers to A:, DKA0: and filemode A — and the drive you were on is the drive you land on.

Install

uv tool install emix-shell     # or: pipx install emix-shell
emix cpm

To try it without installing anything:

uvx --from emix-shell emix cpm

The distribution is emix-shell but the command is emix, which is why uvx needs --from. PyPI rejects the bare name emix as too similar to the existing emux, emx and emi projects.

To update later, ask Emix — it knows how it was installed and offers the matching command rather than guessing:

emix --update

Emix has no runtime dependencies beyond Python 3.11 or newer. If uv warns that ~/.local/bin is not on your PATH, run uv tool update-shell and open a new terminal.

macOS, Linux and Windows, all three covered by CI on Python 3.11 to 3.14, and the Raspberry Pi's own console as well as SSH into it.

Two things are Unix-only. Tab completion and command history need readline, which Windows does not ship — uv tool install "emix-shell[windows]" adds pyreadline3 and restores both. The RunCPM application bridge builds on Windows but is not yet documented for it. WSL gives you the Linux experience unchanged if either matters.

From source

Running from a checkout needs no install step at all:

git clone https://github.com/rdubar/emix && cd emix
./emix cpm

or install the working tree, or the repository directly:

uv tool install .
uv tool install git+https://github.com/rdubar/emix

Use

The current directory becomes the first drive. Mount more with --mount, which is repeatable; drives are named in each personality's own style, so the first mount is A: under CP/M, DKA0: under VMS and filemode A under CMS.

Settings can persist in ~/.config/emix/emix.toml (%APPDATA%\emix\emix.toml on Windows) — the personality to start, drives to mount, screen and hint colours, whether to be strict. Command line always wins.

emix cpm                                  # . becomes A:
emix cpm --mount ~/Documents --mount ~/src  # A: and B:
emix vms --mount ~/Documents              # DKA0:
emix cms --mount ~/Documents              # filemode A
emix cpm -c "DIR *.TXT"                   # run one command and exit

Real CP/M applications

With an emulator installed separately, Emix can open a host document in real CP/M software. Set it up once (see the manual), then:

A>APPS
DDT        DDT.COM        DDT
ED         ED.COM         ED-CPM
MBASIC     MBASIC.COM     MBASIC
TE         TE.COM         TE-CPM

A>ED NEW.TXT

NEW FILE
     : *I
    1:  Dear Gary,
    2:  The BDOS is a triumph.
    3:
     : *E

DOCUMENT SESSION COMPLETE

  CREATED   NEW.TXT
  IGNORED   NEW.$$$  (the application's own backup)

Save these changes to the host? [Y/n]

That is Digital Research's ED.COM from 1982, executing real Z80 instructions, editing a file in your own folder. MBASIC gets you Microsoft BASIC-85 5.29; DDT gets you the debugger.

Only the document you named is staged, so the program cannot see the rest of the folder. The editor's own scratch file is named but not committed. The write is atomic, and is refused if Emix sees that the host file changed while the guest ran (checked before the write, though a write landing inside the final handoff can still be lost).

What is authentic and what is not

Emix aims to be recognisable, and says so when it is not.

Authentic. CP/M's six CCP built-ins are exactly the six it had; PIP and STAT are listed separately because they were transient .COM programs loaded from disk, not built-ins. REN NEW=OLD and PIP DEST=SOURCE keep their surprising destination-first order. DCL verbs abbreviate to any unambiguous prefix. DELETE demands an explicit version, as VMS did. CMS answers Ready; T=... after every command and Ready(00028); after a failure. Error messages follow each system's house format — NO FILE, %RMS-E-FNF, file not found, DMSxxx002E File 'X' not found.

Deliberately not authentic. ERA confirms every erase, where CP/M only confirmed for ERA *.*, because these are your real files. Names that do not fit 8.3 are shown in full rather than truncated, because a listing that names a file you cannot then type is worse than a misaligned column, so a long name is shown as a reversible alias — pyproject.toml lists as PYPROJ_1 TOM, and TYPE PYPROJ_1.TOM reads it back without ever renaming the host file. ABOUT and CREDIT are shared Emix commands in every personality, as are APPS, EXPLAIN, TRANSLATE, BECOME, SETUP and STRICT. HELP, CLS, VER, UNIX and DRIVES are further Emix conveniences and are labelled as such in HELP. File versions display as ;1 but only one copy is stored.

Not yet built. CP/M user areas, VMS directory syntax and real file versions, CMS EXEC and XEDIT, and SAVE, which copied memory Emix does not have. See docs/ROADMAP.md. More speculative jokes, atmosphere and optional AI experiments live in docs/IDEAS.md.

Assistance

Emix helps you find the period command without pretending to be a system that accepted yours:

$ ls
%DCL-W-IVVERB, unrecognized command verb - check validity and spelling
Emix: OpenVMS has no ls. To list the files, use DIRECTORY.

$ DELETE FOO.TXT
%DELETE-W-NOVER, explicit version number required
$ EXPLAIN
Emix: DELETE needs an explicit version, as in FILE.TXT;1. VMS kept every
Emix: version of a file, so a delete without one was too easy to get wrong.

Three rules make this safe to leave on: the authentic response prints first and unaltered, every added line is marked Emix:, and nothing is ever run on a guess — a hint names the command, you type it. That is the difference between assistance that teaches the old system and assistance that replaces it.

Output from commands the original system never had is printed in the hint colour too, so nothing non-period is mistaken for period output. Each personality decides: CP/M's HELP is painted because CP/M had none, while VMS's stays plain because VMS had one.

Emix asks the terminal what colour it is — COLORFGBG first, then an OSC 11 query — and lights the whole session in green phosphor on a dark screen, with hints in amber against it. A light screen, or one that will not say, is left alone: green on white is merely hard to read. The phosphor is set once and inherited, not wrapped around every line, because output meant to read as the machine's own should not be full of escape sequences.

Both ends of that are the bright ANSI colours, not the dim ones: many colour schemes render ANSI green as a muddy yellow-green, which is neither convincing as phosphor nor far enough from amber to read as a different voice. Hints turn the phosphor back on behind them, so a hint interrupts the screen rather than ending it.

--screen cyan (or $EMIX_SCREEN) picks another main colour, --hint-colour bright-white (or $EMIX_HINT_COLOUR) another for the hints, and none turns either off. $NO_COLOR and any non-terminal output disable both, because escape codes in a pipe are corruption rather than decoration.

STRICT ON, or --strict, removes all of it. Scripts and pipes are strict by default, because a script must not depend on a guess. Tab completion stays on either way: it changes what you type, never what runs.

Safety

Emix runs on your real home directory, so the boundaries are explicit and tested:

  • Drives are sealed. Every path is resolved through the host layer and checked against its drive root after symlinks are followed, so a symlink pointing out of a drive is neither readable nor listed. Directory traversal, absolute paths and separators in file names are rejected.
  • No shell is ever invoked. Unknown CP/M commands are offered to the host as executables via subprocess with an argument list. Because there is no shell, |, >, &&, $VAR and backticks are literal arguments rather than operators. Exit Emix when you want a real shell. VMS and CMS do not fall through at all; use RUN/SPAWN and CMS.
  • Case ambiguity fails loudly. On a case-sensitive host holding both readme.txt and README.TXT, Emix reports the ambiguity rather than silently picking whichever the filesystem happened to list first.
  • Destructive commands confirm, and anything but an explicit Y/YES means no.

Erasing a file in Emix erases it on the host. That is the point of the project, and the reason for everything above.

Development

uv sync           # create the environment
uv run pytest     # the full suite
uv run ruff check . && uv run ruff format --check .
uv run mypy       # strict

Layout:

src/emix/
  errors.py         symbolic error codes, worded by each personality
  host.py           drives: containment, case folding, ambiguity
  shell.py          the REPL, verb table, abbreviation, host fallthrough
  cli.py            argument parsing, drive mounting, subcommands
  assist.py         hints, translations and explanations; never executes
  config.py         settings that persist between sessions
  personalities/    cpm.py, vms.py, cms.py — vocabulary and house style
  apps/             document sessions in real historical applications

Adding a personality means one module and one line in personalities/__init__.py. Verbs are methods marked with @verb; the base class handles parsing, dispatch, confirmation, history and errors.

Status

Emix is an early experiment, and says so on PyPI: the classifier is Alpha. Version 0.4 is useful for real file browsing today — on macOS, on Windows, and on a Raspberry Pi 5, where the case-sensitive filesystem is the interesting case and caught a bug that macOS structurally could not. Running real CP/M applications over your own documents works, and needs an emulator you build yourself.

It is one person's project. Bug reports are welcome, and reports of historical inaccuracy are the most welcome of all — if Emix says something the real machine never said, that is a bug, and there is an issue template for it.

Release notes are in CHANGELOG.md. The roadmap covers where it goes next, including whether Emix should eventually execute genuine CP/M .COM binaries in a sandbox — and why that turns out to be less frightening than it sounds.

MIT licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

emix_shell-0.5.0.tar.gz (226.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

emix_shell-0.5.0-py3-none-any.whl (94.4 kB view details)

Uploaded Python 3

File details

Details for the file emix_shell-0.5.0.tar.gz.

File metadata

  • Download URL: emix_shell-0.5.0.tar.gz
  • Upload date:
  • Size: 226.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for emix_shell-0.5.0.tar.gz
Algorithm Hash digest
SHA256 69a083d081f1302802744ffec4aa3b675a439aa68afdbc0162202c4442e70d60
MD5 e591f6f81a2e8c35793c68659017a891
BLAKE2b-256 49406785d8c425418948bcd2e6d17e1d2c791fd888fdc31e4ca41d58e21f5043

See more details on using hashes here.

File details

Details for the file emix_shell-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: emix_shell-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 94.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for emix_shell-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 90c754a4882f141989c2aff8d4f6a72b676677b3c606821028983480bb084b07
MD5 0eb5653ac10cb1cfb31b2ed426032781
BLAKE2b-256 b0e7ef5f1c96346d97e5d11732ea2bcb6933eb2ee1b31788b53d3eb23f0dbcfc

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page