Skip to content

Repository files navigation

┌──────────────────────────────────────────────────────────────┐
│▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▐│
│▐                                                            ▌│
│▐                    ██████╗██████╗  ██████╗                 ▌│
│▐                   ██╔════╝██╔══██╗██╔════╝                 ▌│
│▐                   ██║     ██████╔╝███████╗                 ▌│
│▐                   ██║     ██╔═══╝ ╚════██║                 ▌│
│▐                   ╚██████╗██║     ██████║                  ▌│
│▐                    ╚═════╝╚═╝    ╚═════╝                   ▌│
│▐                                                            ▌│
│▐▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▐│
│                                                              │
│          Version 0.7.0  ·  Cudane Python Subsystem           │
│                                                              │
└──────────────────────────────────────────────────────────────┘

cps is the shared Python subsystem of the Cudane ecosystem. One pyo3-backed engine for plugins, themes and TUIs that every Cudane component embeds — Cesar, Context, Outsider, MCX and Leon — so a single Python skill works everywhere with the exact same contract.

Contents

Overview

The four user-space components of Cudane each grew their own copy of the same Python machinery — src/python/{mod,plugin,theme,tui}.rs — and the copies drifted apart (payloads, message prefixes, descriptor paths). cps is the extraction: one crate, one contract, one engine.

A component embeds cps in four lines:

use cps::{Options, PythonConfig, PythonEngine};

cps::configure(Options::new("context"));
let engine = PythonEngine::new(&PythonConfig::default());

That's it. The interpreter is booted once, a venv is activated if configured, and the configured theme, TUI and plugins are loaded. The same code powers csr, ctx, ous, mcx — and, as a host-side companion, Leon's boot tooling.

Why one shared subsystem

  • One contract. PythonConfig has the identical shape everywhere, so a [python] section means the same thing in every component's config.
  • Unlimited ideas, one surface. A theme or plugin written once works in Cesar's service UI, Context's shell, Outsider's build engine, MCX's package manager and Leon's boot experience.
  • No drift. The five divergent copies are replaced by one crate pinned to a Cargo.lock; every component builds with --locked.
  • Safe by design. The interpreter is only touched when enabled = true, every load is catch_unwind-wrapped, and fallback_on_error = true by default — a broken theme can never take down a host component.

Architecture

┌──────────────────────────────────────────────────────────────┐
│                        cps crate                              │
│                                                              │
│  PythonConfig   config.rs     the shared contract            │
│  PythonEngine   engine.rs     Once boot + venv + load        │
│  ThemeEngine    theme.rs      render_prompt / run            │
│  PluginManager  plugin.rs     hook discovery + registry      │
│  TuiEngine      tui.rs        run() full-screen apps         │
│  Reporter       lib.rs        host-branded messages          │
│  Options        lib.rs        desc dirs + reporter           │
└──────────────────────────────────────────────────────────────┘
        │                          ▲
        │ configure(Options)       │ list/by_name/apply
        ▼                          │
┌─────────────────────────┐  ┌──────────────────────────────┐
│  Host component         │  │  Descriptor files            │
│  (csr/ctx/ous/mcx/lbt)  │  │  ~/.config/<brand>/{t,p}.desc│
└─────────────────────────┘  └──────────────────────────────┘

Two layers live in one crate:

  • The engine (PythonEngine, ThemeEngine::load, PluginManager::load_all, TuiEngine::load) — imports real Python modules and calls them. This is what a running component uses.
  • The registries (ThemeEngine::list, PluginManager::by_alias, TuiEngine::run, and the register/register_desc/unregister family) — a static, in-memory index populated from t.desc / p.desc, used by the component CLIs (cps theme list, csr plugin run, …).

Python contract

A theme is a single .py module that may define (all optional):

Function Signature Purpose
render_prompt render_prompt(**context) -> dict | str returns {lines_above, input_prefix, right_prompt, colors, ...}
render_right_prompt render_right_prompt(**context) -> str right-aligned suffix
render_command_summary render_command_summary(**context) -> str one-line summary after a command
run run() -> bool full-screen mode (called when tui_mode is on)

Context keys passed to every render function include at least cwd, user, host, exit_code, and brand; each component may add more.

A plugin is a .py module whose top-level callables become event hooks (on_startup, on_shutdown, on_command, …). Hooks receive **context.

A TUI is a .py module exposing run().

Full authoring details: docs/PYTHON.md.

Descriptor files

t.desc registers themes and TUIs; p.desc registers plugins. Both are searched, in order, across the desc_dirs configured by the host component (defaults: ~/.config/<brand>, /etc/<brand>, ., plus the current directory). The first file that exists and parses wins.

# t.desc
[theme.cps]
name = "cps"
path = "~/CPS/themes/cps.py"
description = "Default cps theme — cyan accent, host-aware prompt"

[tui.installer]
name = "Installer"
path = "~/CPS/tuis/installer.py"
description = "Full-screen package installer TUI"
# p.desc
[plugin.example]
name = "Example"
path = "~/CPS/examples/example_plugin.py"
aliases = { hi = "echo 'hi from cps example plugin'" }

register/register_desc/unregister write to the in-memory registry; persistent installations are made by writing a descriptor file (or by the component's own CLI, e.g. csr plugin install).

Configuration

[python]
enabled = true
theme = "~/CPS/themes/cps.py"
tui = ""
plugins = ["~/CPS/examples/example_plugin.py"]
fallback_on_error = true
venv_path = "~/venvs/cudane"
tui_mode = false
Key Type Default Meaning
enabled bool false master switch — false never touches the interpreter
theme str "" theme module path
tui str "" TUI module path
plugins list [] plugin module paths
fallback_on_error bool true degrade to native on any Python failure
venv_path str "" optional venv whose site-packages is activated
tui_mode bool false invoke the theme's run() after startup

The cps CLI reads its config from ~/.config/cps/config.toml, /etc/cps/config.toml or ./cps.toml (first match wins), matching how every component loads its own.

Integration

Component Brand Dep Seam
Cesar cesar cps = { path = "../CPS" } csr plugin/theme/tui commands
Context context cps = { path = "../CPS" } prompt rendering + on_startup
Outsider ous cps = { path = "../CPS" } build-aware prompt + theme registry
MCX mcx cps = { path = "../CPS" } package-aware prompt + plugin aliases
Leon lbt cps = { path = "../CPS" } boot companion: splash/theme authoring + BootInfo preview

Each component:

  1. depends on cps via path = "../CPS" and drops its own pyo3 dep;
  2. calls cps::configure(Options::new("<brand>")) once at startup (optionally with a Reporter adapter to keep its own message style);
  3. replaces crate::python::… imports with cps::… and deletes its src/python/.

CLI reference

cps theme    list | apply <name>    | register <name> <path>    | unregister <name>
cps plugin   list | run <alias> …   | register <name> <path>    | unregister <name>
cps tui      list | apply <name>    | register <name> <path>    | unregister <name>
cps engine   [--config <path>]                                   # boot the engine and render a sample prompt

The CLI is both a self-contained demo of the subsystem and the template for the Leon boot companion.

Building

Requires the Cudane musl toolchain (clang targeting x86_64|aarch64-unknown-linux-musl) and a Rust toolchain with pyo3 (Python 3 headers available).

make            # or: ninja | meson setup build && ninja -C build
make clippy     # cargo clippy --all-targets -- -D warnings
make test       # cargo test --locked

All build paths pass --locked and target the musl sysroot, exactly like the rest of the ecosystem. cross.txt is regenerated with ./gen-cross.sh.

Installation

make install DESTDIR=/mnt/rootfs        # installs /system/bin/cps + share/cps data

Installs the cps binary to ${PREFIX}/bin/cps and its themes, descriptors and sample config to ${PREFIX}/share/cps.

Testing

cargo test --locked

The suite covers the pure-Rust behaviour (config parsing, ~ expansion, the static registries, the disabled-engine path) and runs without a live interpreter. Engine tests that touch Python live alongside the component suites.

Structure

src/lib.rs          Reporter trait, Options, configure, module wiring
src/config.rs       PythonConfig — the shared contract
src/engine.rs       PythonEngine — Once boot + venv + load
src/paths.rs        ~ expansion, desc candidates, venv activation
src/theme.rs        ThemeEngine, ThemeResult, theme registry
src/plugin.rs       PluginManager, PluginEntry, plugin registry
src/tui.rs          TuiEngine, TuiEntry, tui registry
src/bin/cps.rs      reference CLI (theme/plugin/tui/engine)
themes/             cps.py, minimal.py
examples/           example_plugin.py
tests/              integration tests (no interpreter needed)
docs/               authoring guide

Dependencies

  • pyo3 0.25 — Python bindings
  • serde / toml 0.8 — descriptor + config parsing
  • clap 4 — the reference CLI

Contributing

cps lives at ~/CPS. It is versioned with the ecosystem (0.7.0) and follows the Cudane build conventions. Keep the engine feature-complete but dependency-free of any one component: host-specific behaviour belongs behind Reporter / Options, never hardcoded.

MIT License · Copyright (c) 2026 Mapuse

About

Cudane's Python Subsystem, Open Python Customizing Framework for Cudane's Ecosystem.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages