feat: add CLI entry point (muxplex serve, muxplex install-service) and __main__.py

This commit is contained in:
Brian Krabach
2026-03-28 02:18:58 -07:00
parent a70e8f6f52
commit c2087083c2
3 changed files with 188 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
"""Allow running muxplex as: python -m muxplex"""
from muxplex.cli import main
main()
+85
View File
@@ -0,0 +1,85 @@
"""muxplex CLI — web-based tmux session dashboard."""
import argparse
import os
import sys
from pathlib import Path
# Module-level path constants (overridable in tests via monkeypatch)
_system_service_path = Path("/etc/systemd/system/muxplex.service")
def serve(host: str = "0.0.0.0", port: int = 8088) -> None:
"""Start the muxplex server."""
import uvicorn # noqa: PLC0415
os.environ.setdefault("MUXPLEX_PORT", str(port))
from muxplex.main import app # noqa: PLC0415
print(f" muxplex → http://{host}:{port}")
uvicorn.run(app, host=host, port=port, log_level="warning")
def install_service(*, system: bool = False) -> None:
"""Install muxplex as a systemd service."""
executable = sys.executable
unit = f"""\
[Unit]
Description=muxplex — web-based tmux session dashboard
After=network.target
[Service]
Type=simple
ExecStart={executable} -m muxplex
Restart=on-failure
RestartSec=5s
Environment=PATH={os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")}
[Install]
WantedBy={"multi-user.target" if system else "default.target"}
"""
if system:
path = _system_service_path
reload_cmd = (
"sudo systemctl daemon-reload && sudo systemctl enable --now muxplex"
)
else:
path = Path.home() / ".config" / "systemd" / "user" / "muxplex.service"
path.parent.mkdir(parents=True, exist_ok=True)
reload_cmd = (
"systemctl --user daemon-reload && systemctl --user enable --now muxplex"
)
path.write_text(unit)
print(f"Service file written to {path}")
print(f"Enable with:\n {reload_cmd}")
def main() -> None:
"""CLI entry point."""
parser = argparse.ArgumentParser(
prog="muxplex",
description="muxplex — web-based tmux session dashboard",
)
parser.add_argument(
"--host", default="0.0.0.0", help="Bind host (default: 0.0.0.0)"
)
parser.add_argument("--port", type=int, default=8088, help="Port (default: 8088)")
sub = parser.add_subparsers(dest="command")
sub.add_parser("serve", help="Start the server (default)")
svc = sub.add_parser("install-service", help="Install systemd service unit")
svc.add_argument(
"--system", action="store_true", help="System-wide (requires sudo)"
)
args = parser.parse_args()
if args.command == "install-service":
install_service(system=args.system)
else:
serve(host=args.host, port=args.port)
+98
View File
@@ -0,0 +1,98 @@
"""Tests for muxplex/cli.py — CLI entry point."""
from pathlib import Path
from unittest.mock import patch
def test_cli_module_importable():
"""muxplex.cli must be importable."""
from muxplex.cli import main # noqa: F401
def test_main_calls_serve_by_default():
"""Calling main() with no args must invoke serve()."""
from muxplex.cli import main
with patch("muxplex.cli.serve") as mock_serve:
with patch("sys.argv", ["muxplex"]):
main()
mock_serve.assert_called_once_with(host="0.0.0.0", port=8088)
def test_main_passes_custom_host_and_port():
"""main() with --host/--port must forward them to serve()."""
from muxplex.cli import main
with patch("muxplex.cli.serve") as mock_serve:
with patch("sys.argv", ["muxplex", "--host", "127.0.0.1", "--port", "9000"]):
main()
mock_serve.assert_called_once_with(host="127.0.0.1", port=9000)
def test_main_install_service_subcommand():
"""main() with 'install-service' must invoke install_service()."""
from muxplex.cli import main
with patch("muxplex.cli.install_service") as mock_install:
with patch("sys.argv", ["muxplex", "install-service"]):
main()
mock_install.assert_called_once_with(system=False)
def test_main_install_service_system_flag():
"""main() with 'install-service --system' passes system=True."""
from muxplex.cli import main
with patch("muxplex.cli.install_service") as mock_install:
with patch("sys.argv", ["muxplex", "install-service", "--system"]):
main()
mock_install.assert_called_once_with(system=True)
def test_install_service_user_mode_writes_unit_file(tmp_path, monkeypatch):
"""install_service(system=False) writes a unit file to ~/.config/systemd/user/."""
from muxplex.cli import install_service
fake_home = tmp_path / "home"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home))
install_service(system=False)
unit_path = fake_home / ".config" / "systemd" / "user" / "muxplex.service"
assert unit_path.exists()
content = unit_path.read_text()
assert "[Unit]" in content
assert "[Service]" in content
assert "[Install]" in content
assert "muxplex" in content
assert "default.target" in content
def test_install_service_system_mode_target(tmp_path, monkeypatch):
"""install_service(system=True) targets multi-user.target in the unit file."""
from muxplex.cli import install_service
# Redirect the system path to tmp so we don't write to /etc
unit_path = tmp_path / "muxplex.service"
monkeypatch.setattr("muxplex.cli._system_service_path", unit_path)
install_service(system=True)
assert unit_path.exists()
content = unit_path.read_text()
assert "multi-user.target" in content
def test_dunder_main_calls_main():
"""python -m muxplex must call cli.main()."""
with patch("muxplex.cli.main") as mock_main:
# Simulate `python -m muxplex` by exec'ing __main__.py
import muxplex.__main__ # noqa: F401
# The import itself calls main() at module level
# Re-exec to test:
mock_main.reset_mock()
exec(Path("muxplex/__main__.py").read_text())
mock_main.assert_called_once()