Emix
Emix lets a modern Unix machine pretend to be an older computer.
It is not a CPU emulator and it does not run historical binaries. It presents the commands, syntax, output formats and error messages of historical systems while operating on ordinary host files with ordinary host programs underneath. Your files stay real files.
Three 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 |
They are not three programs. They are three 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.
Install
Not yet released. Nothing has been published to PyPI yet, so the commands below that name
emix-shellwill not resolve until 0.2.0 ships. Run from a checkout meanwhile.The distribution is called
emix-shell; the command it installs isemix. PyPI rejects the bare nameemixas too similar to the existingemux,emxandemiprojects.
Emix has no runtime dependencies beyond Python 3.10 or newer.
Run from a checkout, with no install at all:
git clone https://github.com/rdubar/emix && cd emix
./emix cpm
Install it as a command, from a checkout:
uv tool install . # or: pipx install .
emix cpm
Install on another machine — build a wheel and copy it across. The wheel is pure Python, so one build serves x86-64 and Apple silicon and Raspberry Pi alike:
uv build
scp dist/emix-*.whl pi:/tmp/
ssh pi 'uv tool install /tmp/emix-*.whl'
Once released, and once the repository is public, these will work:
uv tool install emix-shell # from PyPI
uv tool install git+https://github.com/rdubar/emix # from source
uvx --from emix-shell emix cpm # without installing
Note the --from in the uvx line: the distribution is emix-shell but the
command is emix, so uvx needs telling which package provides it.
If uv warns that ~/.local/bin is not on your PATH, run
uv tool update-shell and open a new terminal.
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.
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
A CP/M session:
EMIX 0.2.0
CP/M 2.2 PERSONALITY
A: /Users/rdubar/dev/emix
TYPE HELP FOR AVAILABLE COMMANDS.
A>DIR *.MD
A: README MD A: ROADMAP MD
A>PIP NOTES.TXT=README.MD
A>STAT
A: R/W, SPACE: 96,508,384K
A>python3 hello.py
Hello from Unix
A>EXIT
RETURNING TO UNIX.
The same drive under DCL:
$ DIRECTORY/SIZE
Directory DKA0:[000000]
README.MD;1 6
ROADMAP.MD;1 9
Total of 2 files, 15 blocks.
$ DELETE README.MD
%DELETE-W-NOVER, explicit version number required
and under CMS, where a file is three words and the system answers Ready;:
LISTFILE
README MD A1
ROADMAP MD A1
Ready; T=0.01/0.01 21:42:19
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. HELP, CLS,
VER, UNIX and DRIVES are 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, reversible 8.3 aliases, VMS directory
syntax and real file versions, CMS EXEC and XEDIT. See
ROADMAP.md.
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
subprocesswith an argument list. Because there is no shell,|,>,&&,$VARand backticks are literal arguments rather than operators. Exit Emix when you want a real shell. VMS and CMS do not fall through at all; useRUN/SPAWNandCMS. - Case ambiguity fails loudly. On a case-sensitive host holding both
readme.txtandREADME.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/YESmeans 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 # 62 tests
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 and drive mounting
personalities/ cpm.py, vms.py, cms.py — vocabulary and house style
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, version 0.2, not yet released. It is useful for
real file browsing today, and is tested on macOS and on a Raspberry Pi 5 — the
case-sensitive filesystem there is the interesting case. The roadmap covers where it goes next, including the
question of whether it should eventually execute genuine CP/M .COM binaries
in a sandbox — and why the answer 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file emix_shell-0.2.0.tar.gz.
File metadata
- Download URL: emix_shell-0.2.0.tar.gz
- Upload date:
- Size: 79.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c973575427f3cd5559e10127392b8894e519763487bba8a46db758296320730a
|
|
| MD5 |
4044a6e420649184a6996c9f597493a1
|
|
| BLAKE2b-256 |
1ef2c8c55f3719f8fb76404999763092fc53c234a39e47c1274326be0f75b701
|
File details
Details for the file emix_shell-0.2.0-py3-none-any.whl.
File metadata
- Download URL: emix_shell-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34b4cd09e7b2215737920c235234eb8c961c14d02ac2eaf858d2056355f8e121
|
|
| MD5 |
918dc2c4ba76359a209da2d34676bc48
|
|
| BLAKE2b-256 |
31a4e6da2a1154518986b76585de21143ea477682f904270257132c5f5c82324
|